Migrating from ink! 3.x to 4.0
We've made a couple of breaking changes from ink! 3.x to ink! 4.0. On this page we outline how you can migrate existing clients and contracts from 3.x to 4.0.
This migration guide is only for your code base!
If you have an existing contract on-chain you cannot just upgrade the code on-chain ‒ you also have to migrate your data, since the way ink! 4.0 stores data and reads it (i.e. the storage layout) changes from ink! 3.x to 4.0.
ink! 4.0 is compatible with:
- Stable Rust >= 1.63.0
Together with ink! 4.0 we've released
You have to use this latest version of
cargo-contract for ink! 4.0
You can upgrade via:
cargo install cargo-contract --force --version 2
Make sure that e.g. your CI also uses
cargo-contract 2 with ink! 4.
If you have wrapper scripts around
cargo-contract you should
ensure that this version is enforced, otherwise users will get an error.
cargo-contract no longer requires
wasm-opt as an
external dependency. We required those because of
(which is part of
binaryen). Fortunately we were able to find a way of
wasm-opt now as part of the
stable instead of
ink! 4.0 and
stable Rust now.
This means no more
cargo +nightly contract is required, you
can just use a stable Rust toolchain now (>= Rust 1.63).
ink_lang crate has been replaced in #1223
by a new top level
ink crate. All existing sub-crates are reexported and should be used via
ink crate, so e.g.
ink::env instead of
ink_env. Contract authors should now import
the top level
ink crate instead of the individual crates.
Cargo.tomlReplace all individual
ink_*crate dependencies with the
- In the contract source:
- Remove the commonly used
use ink_lang as inkidiom.
- Replace all usages of individual crates with reexports, e.g.
- Remove the commonly used
Storage API + Layout
With #1331 the way
ink! reads and writes
to a contract's storage changed. Storage keys are generated at compile-time, and user facing
abstractions which determine how contract data is laid out in storage are different now.
ink_lang::utils::initialize_contractin constructors. See
erc20and other examples which use a
PackedAllocatehave been removed. It's best to see the documentation of the new storage abstraction for how to migrate.
ink! uses a bump allocator by default, additionally we supported another allocator
wee-alloc) through a feature flag.
wee-alloc is no longer maintained and
we removed support for it in #1403.
As part of #1233
eth_compatibility crate was removed. The
function from it can now be found in the
ink_env::ecdsa_to_eth_address(&pub_key, &mut output);
The function signature of
Mapping::insert(key, val) changed to
Mapping::insert(key, val) -> Option<u32>.
The return value is the size of the pre-existing value at the specified key if any (in bytes).
Two new useful functions were added:
Mapping::take()to get a value while removing it from storage in #1461.
In case you were working around those two functions you can now
use them directly; they are more gas-efficient than e.g. executing
get(key).is_none() instead of
Storage functions in
As part of #1224 the return type
was changed to return an
Option<u32> instead of
A new function
We had to remove the
function (in #1442).
This function allowed contract developers getting random entropy.
There is unfortunately no way how this can be done safely enough
with built-in Substrate primitives on-chain currently. We're
following the recommendation of our auditors to remove it.
The alternative right now is to provide random entropy off-chain to the contract, to use a random entropy oracle, or to have a chain-extension that does this, in case the chain has a possibility to do so.
We hope to bring this function back in a future release of ink!, the best hope right now is that it could come back with Sassafras, a block production protocol for future versions of Polkadot.
If you're interested in more information on this check out
the Substrate PR which
deprecated the random interface of
Constructors can now return
With #1446 we introduced
the possibility for constructors to return either
Self (as usual) or
This enables contract developers to bubble up encoded error objects to clients/frontends about a failure. In ink! 3.x it was only possible to panic in the constructor in case an error occurred, resulting in loss of this information.
returns_result flag has been removed from the
#[ink(extension = …)] attribute in
We now infer this information at compile time. If
handle_status is set to
the return type will still be wrapped into
Result as before.
Contract Metadata (ABI)
The most detailed way to grasp what changed is to look at this PR, which updated the metadata page in our documentation.
Add support for language level errors (
Under the hood, ink! now generates code that results in each message
and constructor returning a
Result<Message::Output, LangError> (or
This happens even if the message/constructor doesn't have a return type,
we default to the unit type
() in that case.
is a type of error which doesn't originate from the contract itself,
nor from the underlying execution environment (so the Contracts pallet
in this case).
An example of where this would arise is if a caller tries to use a non-existent message selector for a contract. Previously, the contract would trap and not allow the caller to do any sort of error handling if it encountered a non-existent selector.
This change doesn't affect how you write a contract! It affects clients and frontends though, since it breaks the API in two ways:
first, all contract messages now have a
Result return type, and second a new field,
lang_error, will be introduced as part of the contract spec. The second change allows
other languages (such as Solang) to use an equivalent
Click here for a snippet of the new metadata for the Flipper contract.
" Flips the current value of the Flipper's boolean."
As part of #1313 the ink! ABI was changed to have a proper version field as part of the ink! metadata object. This enables querying the ABI version in a less-ambiguous way.
The Storage Layout (
The storage layout under the
storage key changed for v4. If you have an application
that is using it consider reading the updated documentation:
In #1255 we removed the
Default implementation of
AccountId returned the zero-address, which is
problematic since the
zero-address in the
ed25519 curves has a known private key.
Developers commonly reach for defaults, and the zero-address in particular, making it an unsafe trait implementation to have given the consequences.
Imagine a developer sending tokens to the zero-address to be burned, only to find that they've been stolen because the private key is known.
If you were previously using
Default implementation in your code you
have a couple of different options for how to move forward. These will depend on what
exactly you were using the zero-address for.
If you were using it as a burn address:
- You can pick another address to use, assuming that you've actually picked a random address
- Consider a solution that involves reducing total issuance, instead of transferring tokens to a random address
If you were using it as a privileged account:
- Change the account
- Add checks to ensure that calls coming from the zero-address are rejected
You should also now consider dealing with
Option<AccountId>'s. This is
more idiomatic Rust, and also conveys the meaning of a "null" or "empty" address much
Updates to the
There's been several changes to the
In #1604 we renamed the
CallBuilder::fire() method to
This brings more consistency across our APIs which were already using the
In #1512 and #1525
we added support for handing
LangErrors from the
If you want to handle errors from either
Builder you can use the new
Because of the addition of those methods we also removed any error handling from the
try_ methods in #1602. This means
CreateBuilder::instantiate() methods return values
directly, and panic when they encounter an error.
Lastly, in #1636 we added two methods to
CallBuilder to streamline
Callyou can use
DelegateCallyou can use
[lib.name] from contract manifest
Earlier versions of
cargo-contract required that these two fields were specified in the
contract manifest explicitly, as follows:
name = "flipper"
path = "lib.rs"
crate-type = [
# Used for normal contract Wasm blobs.
# Use to generate ABI
However, with with cargo-contract#929 we changed this behavior to:
- Use the contract name by default, removing the need for the
- Compile contracts as
rlibs by default, and automatically changing to
This means that your new manifest should look like:
path = "lib.rs"