Migration: ink! v5 → v6
We've made a number of breaking changes from ink! 5.x to ink! 6.0. On this page we outline how you can migrate existing dApps and contracts.
The biggest change is that we've migrated from pallet-contracts +
WebAssembly (executed in wasmi) to pallet-revive +
RISC-V (executed in PolkaVM).
This is a major breaking change, ink! v6 is only compatible with cargo-contract >= v6
and chains that include pallet-revive.
We did a detailed write-up of the background to this development and the reasoning here.
You can find the full changelog of the 6.0 release here.
This migration guide only considers your code base! Not your storage data!
If you have an existing contract on-chain you might not be able to just upgrade the code on-chain, you possibly also have to migrate your storage data.
Compatibility
polkadot-sdk:polkadot-sdk/cbab8ed4be1941420dd25dc81102fb79d8e2a7f0(Oct 15, 2025)- For the latest compatibility requirements of Rust,
cargo-contractand theink-node, see the setup instructions.
How do I find out if a chain is compatible with ink! 6.0?
You can query contracts::palletVersion() via the chain state RPCs. It has to
be >= 9 for ink! 5.0 to be compatible, if you don't use any of the four functions
mentioned above.
For the above mentioned four functions please see the respective sections on this page,
there we explain how to find out if a chain supports them there.
You can use the polkadot.js app to connect to the chain and check if
reviveApi is available under Developer » Runtime calls.
The following chains are in production and support ink! 6.0.
Important Changes
We had to introduce a number of changes that require you to manually upgrade your contract from 5.x to 6.0. The steps are explained in this section.
Restrict which cfg attributes can be used
This change was done as a recommendation from the ink! 5.x audit. In a nutshell it prevents developers from hiding functionality in a contract, that would not be visible in the metadata (so e.g. on a block explorer). The relevant PR is #2313.
From ink! 6.0 on only these attributes are allowed in #[cfg(…)]: - test - feature (without std) - any - not - all
Metadata Changes
The field source.wasm was renamed to source.contract_binary.
Types
Contract Balance: U256
For the type of a contract's balance, pallet-revive uses depending on the context
- either the configured
pallet_revive::Config::Currencytype (which corresponds to theink::Environment::Balancetype. - or a hardcoded
U256(which corresponds to what Ethereum uses). In this alpha release we just adhere to requiring the types thatpallet-reviveuses. In an upcoming beta release this could be simplified to reduce UX friction by just using one type everywhere and converting to thepallet-reviveone.
Contract Address: Address / H160
For a contract's account, pallet-revive is using either the configured AccountId type
of the polkadot-sdk runtime, or H160.
Address is a more semantically named type alias for H160 defined in ink_primitives,
and re-exported in the ink crate.
Finding the Address/H160 for an AccountId is done via an address derivation scheme
derived in #7662.
After instantiating a contract, the address is no longer returned by pallet-revive.
Instead one has to derive it from given parameters (see the linked PR). cargo-contract
does that automatically.
For contract instantiations and contract calls the pallet requires that a 1-to-1 mapping
of an AccountId to an Address/H160 has been created. This can be done via the
map_account/unmap_account API.
The PR #6096 contains more
information.
Besides the publicly exposed crate functions, we've introduced a new subcommand
cargo contract account that allows resolving the H160 contract address to the
Polkadot SDK AccountId which it is mapped to.
Contract Hash: H256
For a contract's hash value, pallet-revive uses a fixed H256, Previously,
the ink::Environment::Hash type referenced the hash type being used for the
contract's hash. Now it's just a fixed H256.
Contract delegates can no longer be done by code
In pallet-contracts (and hence up until ink! v5), a pattern for upgradeable
contracts was to delegate the contract execution to a different code, e.g. to
a new version of the contract's code.
This distinction of contract code that was uploaded to a chain vs. an instantiated
contract from this code no longer exists in pallet-revive. If you want to
delegate the execution, you will have to specify another contract's address
to which code you want to delegate to. This other contract needs to be instantiated
on-chain.
For the execution, the context of the contract that delegates will continue to be used (storage, caller, value).
Specifically the delegate API changed like this:
/// ink! v5
#[derive(Clone)]
pub struct DelegateCall<E: Environment> {
code_hash: E::Hash,
call_flags: CallFlags,
}
/// ink! v6
#[derive(Clone)]
pub struct DelegateCall {
address: H160,
flags: CallFlags,
ref_time_limit: u64,
proof_size_limit: u64,
deposit_limit: Option<[u8; 32]>,
}
Feature ink/unstable-hostfn
In pallet-revive a number of functions can only be called by smart contracts
if the chain that the pallet is running on has enabled the feature
pallet-revive/unstable-hostfn.
This feature is not enabled on Kusama or Westend!
It is enabled for the ink-node version that we linked above, and on Paseo Passet Hub.
New debugging workflow
Previously pallet-contracts returned a debug_message field with contract
instantiations and dry-runs.
Whenever ink::env::debug_println was invoked in a contract, ink! wrote debugging
info to this field. This functionality has been removed. Instead pallet-revive now
supports other means of debugging.
The most relevant new debugging workflow is the tracing API. There are a number
of PRs that implemented it, so we won't link a specific one here. A good starting
point to look deeper into it is the tracing.rs.
We have implemented barebones support for this tracing API in the 6.0.0-alpha
versions of ink! + cargo-contract. But it's really barebones and should
certainly be improved before a production release.
We've updated the Debugging chapter of this documentation
to reflect the new workflow.
We've also added a contract example to illustrate these new debugging strategies:
debugging-strategies.
Removed Events
In #7164, Parity removed
most smart-contract-specific events: Called, ContractCodeUpdated, CodeStored,
CodeRemoved, Terminated, Instantiated, DelegateCalled,
StorageDepositTransferredAndHeld, StorageDepositTransferredAndReleased.
The ContractEmitted event (for events a contract emits) is still available.
no_main
Previously ink! contracts started with this line:
#![cfg_attr(not(feature = "std"), no_std)]
This line instructs the Rust compiler to not link the Rust standard library with your contract. If you want to know about why: we have an entry "Why is Rust's standard library (stdlib) not available in ink!?" in our FAQ.
With ink! v6, an additional crate-level attribute needs to be set:
#![cfg_attr(not(feature = "std"), no_std, no_main)]
It instructs the compiler not to use the default fn main() {} function as the
entry point for your smart contract. This is needed because PolkaVM uses a different
entry point (the deploy function).
substrate-contracts-node can no longer be used
The substrate-contracts-node is still maintained by Parity for ink! v5 and
pallet-contracts, but it does not support pallet-revive.
We've set up a new project in its place: ink-node.
As before, it functions as a simple local development node.
It contains pallet-revive in a default configuration.
You can find binary releases of the node here.
Solang can no longer be used
It was previously possible to interact with Solidity contracts compiled via the
Solang compiler. As we have moved from WebAssembly/pallet-contracts to
PolkaVM/RISC-V/pallet-revive, users who want to deploy Solidity will use
the Parity revive compiler. It takes
Solidity contracts and compile them into RISC-V for PolkaVM.
payable messages are now considered state-mutating
To align with how Solidity is treated by pallet-revive, we decided on this
breaking change.
The implication is that you can no longer mark &self ink! messages as payable,
they have to be &mut self now.
#[ink(message, payable)]
-pub fn my_message(&self) {}
+pub fn my_message(&mut self) {}
Removed call_runtime and Chain Extensions
The pallet-revive no longer provides a host function to call into the runtime.
Parity removed this feature as the runtime can change and changes could potentially
break already deployed contracts.
The way to go instead would be to create a precompile contract) and have your contract invoke either a cross-contract call or a delegate call into that one.
This is also the migration path recommended for Chain Extensions.
Renamed ink::contract_ref! to ink::contract_ref_from_path!
This was necessary because we introduced a new #[ink::contract_ref] attribute.
The PR #2648 contains more information
about the motivation and benefits of this change.
Sandbox was moved out of ink_e2e
We moved the sandbox testing environment to a separate crate.
It changes the current behaviour from:
[dev-dependencies]
ink_e2e = { version = "…", feature = "sandbox" }
to
[dev-dependencies]
ink_e2e = { version = "6.0.0-beta" }
ink_sandbox = { git = "https://github.com/use-ink/ink.git", branch = "6.0.0-beta" }
In the tests, you need to apply this change when using sandbox. Instead of:
#[ink_e2e::test(backend(runtime_only(sandbox = sandbox_runtime::ContractCallerSandbox)))]
Change to:
#[ink_sandbox::test(backend(runtime_only(
sandbox = sandbox_runtime::ContractCallerSandbox,
client = ink_sandbox::SandboxClient
)))]
Interesting New Features
Solidity ABI compatible ink! contracts
We are introducing an abi field in a custom ink-lang table
in the package.metadata table of a contract's manifest
file (i.e. the Cargo.toml file).
It allows building your contract in Solidity ABI compatibility mode
when declared as follows:
[package.metadata.ink-lang]
abi = "sol"
The implication of supporting Solidity ABI encoding is that all types used as constructor/message argument and return types, and event argument types must define a mapping to an equivalent Solidity ABI type. You won't be able to use Rust types for which no mapping to a Solidity ABI type is defined. An error about a missing trait implementation for this type will be thrown. You can learn more about Rust/ink! to Solidity ABI type mapping here.
This is similar to the requirement to implement scale::Encode and scale::Decode
for Rust types used in the public interfaces of ink!/"native" ABI encoded contracts.
The default value for abi is currently "ink",
but we might change this before a production release.
The other option is abi = "all". In this mode, the generated contract
will support both the ink! and Solidity ABI, however, the contract size
will get larger. You can learn more about supported ABI modes here.
Cross-contract calling Solidity contracts
CallBuilder now allows you to call contracts that are Solidity ABI encoded.
You can learn more here.
Generate Solidity metadata for a Solidity ABI compatible ink! contract
We added a new subcommand:
cargo contract build ---metadata <ink|solidity>
You can learn more here.
Ability to build contract with features during E2E tests
We've added the possibility to set a feature to build a contract with during e2e tests:
#[ink_e2e::test(features = ["debug-info"])]
This allows for e.g. emitting debug events in the contract, which
you can then check for in testing.
Please see our debugging-strategies
example for a complete explainer.
We've added a page Debugging » Events to this documentation.
We've also added a contract example that illustrates the usage:
debugging-strategies.