Top 5 ink! Caveats that Make Smart Contract Development a Challenge
Table of content
ink!, previously known as pDSL, is a smart contract development language that emerged in the blockchain development arena in late 2018 – early 2019. In a nutshell, ink! is an embedded domain-specific language (eDSL) used for writing Wasm smart contracts based on the Rust programming language. Designed to be as similar to Rust as possible, ink! used this language’s attribute macros to tag Rust-written components into smart contract elements. It is widely used in Polkadot’s smart contracts, including a large number of NFT marketplace projects, ensuring the correctness and efficiency of building the contracts backend.
Developers generally praise Substrate and its ink! component for ease of use and versatility of features, enabling end-to-end blockchain setup. However, there are still some serious issues developers encounter when writing smart contracts in ink!. This article compiled by a team of 4IRE blockchain development experts aims to describe them and suggest potential solutions to these issues.
Ink! Smart Contracts
Overall, ink! does not differ much from other smart contract development languages, offering the same features and components for their construction, such as events, storage, internal and public functions, coupled with a robust deployment/construction function.
Ink! features of significance include its mutability and visibility, allowing the incorporation of access to typical Substrate information units like “AccountID,” “Hash,” and “Balances.” Global variables are also accessible through ink!-developed contracts.
Benefits of ink!
Rust is known as a popular programming language due to its simplicity and ease of use. Thus, smart contract development with ink! is compliant with compile-time overflow/underflow safety standards, though developers can also customize their settings to include checked, wrapped, and saturated math functions.
Ink! also allows comprehensive preliminary testing of the developed smart contract via an in-built test environment operating on the Rust framework. In this way, smart contract coders can easily check whether their product works as planned without loading it to external testing platforms.
As Rust is a dynamically developing language, ink! borrows its most effective features and updates automatically, which is an added bonus for developers receiving a robust, ever-improving set of smart contract development tools free of charge.
Ink! vs. Solidity
Comparing ink! with another popular smart contract coding option, Solidity, one can see that the latter doesn’t have an overflow protection layer and offers only one constructor function (while ink! may have multiple). However, ink! also has some limitations as compared to Solidity, such as the absence of interfaces (which are soon planned for launch) and unavailability of multi-file projects.
Schedule a meeting with our CTO
Still have questions or concerns? We’re ready to discuss your project in detail to make it more manageable and understandable together.

What’s the trouble with ink!?
Along with numerous benefits enumerated above, ink! still has some flaws that coders planning to use it should consider. These cons may urge you to think of other development options, given that some caveats create considerable challenges that not every developer is ready (or has the expertise) to handle.
#1 No cross-contract error handling and hard debugging
Error handling is a pain when it comes to ink!. There are no reverts in ink! like there are in Solidity, which forces developers to use `panic!` in case some conditions are not met (e.g., insufficient balance for a transfer of a token).
This approach deprives coders of the ability to handle errors in a deterministic and Rust-native way. They cannot use `Result`’s because returning the `Err` value from a transaction does not guarantee that they revert. Besides, the inclusion of ink!’s `revert_on_err` annotation leads to the behavior similar to `assert`’s.
This inconsistency results in the `ContractTrapped` error on client-side, which practically carries no information. The fix to the problem exists, but it lies rather deep, in `pallet-contracts`. There is yet no protocol that could handle error message passing between a contract and the runtime, which means there is no way of knowing why the contract failed.
The problem is quite serious for smart contract developers, as the fundamental value and meaning of smart contracts is the infallibility in confirming and verifying transactions between blockchain users. Once smart contracts fail at any stage of their development or operation, the validity of the entire smart contract approach is undermined, posing the blockchain users’ interactions under risk. However, the community of ink! core developers is already in a search for options, with error forwarding solutions offered on the ink! level.
#2 Heavy macro logic
ink! is convenient in its usage for simple needs and contracts, as it is high-level and concise. That being said, the logic behind `#[ink::contract]` and other macros is hard to understand, and requires the engineer to possess deep Rust knowledge to process the source code.
Moreover, `#[ink(constructor)]`, `#[ink(message)]` and other pseudo-macros are not even valid macros that exist in some crates. Instead, they are internal markers which `#[ink::contract]` expands during compilation. This means that the Rust compiler cannot really help the developer when an error originates in a macro, and the only helping hand left is *custom* error messages from ink! macros.
/// Ensures that the given slice of items contains at least one ink! message.
fn ensure_contains_message(
module_span: Span,
items: &[ir::Item],
) -> Result<(), syn::Error> {
let found_message = items
.iter()
.filter_map(|item| {
match item {
ir::Item::Ink(ir::InkItem::ImplBlock(item_impl)) => {
Some(item_impl.iter_messages())
}
_ => None,
}
})
.any(|mut messages| messages.next().is_some());
if !found_message {
return Err(format_err!(module_span, "missing ink! message"))
}
Ok(())
}
One way to debug the macro-heavy code that we suggest is to compile code with macro expansion: `cargo rustc –lib — -Z unstable-options -Z macro-backtrace –pretty=expanded`. By default, this approach would involve printing compiled code to stdout, so forward output to a file like this: `cargo rustc –lib — -Z unstable-options -Z macro-backtrace –pretty=expanded > compiled.rs` in case you want to view it after.
This operation is cumbersome and time-consuming; it requires a nightly toolchain, so be sure to add `+nightly` after `cargo` if nightly is not your default toolchain.
As far as solutions go, there’s not much that we can do here. Macros are inherently hard to understand, especially when they do something as serious as compiling a smart contract. Still, the ink! team provides great error messages and good usage documentation.
#3 Poor extensibility
Another serious issue a smart contract developer may come across when it comes to ink! is poor extensibility. Extensibility is a systems design principle contributing to future growth, determined by the developer’s degree of effort required to implement the language’s extension. The latter is implemented via new feature addition, modification of functions, or the creation of new functional units. Thus, an extensible system’s operations are insignificantly affected by new or modified functionality.
When applying the principle of extensibility to ink!, one can see that this language is still too rigid in terms of tolerating extensions. When designing dApps or standalone contracts, developers might find themselves writing the same repetitive code in different contracts. ink!’s support of inheritance right now is quite raw, and there is no way to inherit from a contract like in Solidity.
One solution to this is using traits, but they do not support data inheritance (Rust, in general, doesn’t), which is important for smart contracts. There have been different attempts to resolve this, like the creation of OpenBrush contracts with the traits definition feature for standard tokens and default implementation guidance for those using Rust. The Metis community on GitHub also worked on this problem and developed a more flexible ink! implementation solution for newly generated ink! contract standards.
Both OpenBrush and Metis libraries use macros on top of ink!’s macros and aim to provide extensibility to ink!, as well as common abstractions typical to the Ethereum ecosystem (token, non-fungible token, multi-token etc.).
#4 Inability to define separate events
Another flaw worth mentioning here is that ink! does not support defining events outside the `#[ink::contract]` module. The reason for that is prefixing: every item that is contained in a contract is prefixed by a respective qualified path, and events are not an exception to this rule.
Since they must be contained in `#[ink::contract]`, they cannot be reused across contracts, as ContractOne::Transfer and ContractTwo::Transfer would technically be different events.
This specialty of the smart contract building process with ink! makes working with topics and indexed data harder, too.
As for this issue, the future is promising: Robbepop, who is the ink! core member, recently announced that ink! will implement new syntax that would allow for shared event definitions. According to his information, the innovation would allow ink! developers to assume greater control over event definitions and share the latter between multiple projects.
#5 No reference implementations for typical use cases
Considering this caveat, one should keep in mind that ink! is still early in its development, which means the ecosystem around the language is pretty small. Thus, one particular problem is the lack of reference implementations and examples in ink!, let alone example dApps, which use ink!.
Reference implementations and examples are really helpful to get started with any language, and that surely applies to ink!. Thus, the establishment of a vibrant ink! community, with code implementation use cases, discussion of project-related issues, and in-depth support from core members would become a viable solution to the current challenges experienced by pioneering ink! coders left without consulting and troubleshooting in critical moments of smart contract development.
To add to the things mentioned above, ink!-related stuff is relatively hard to find through search engines. That also makes developer experience with ink! worse. However, the ink! creators are already working on this problem, suggesting a single source with standardized documentation, reference implementations, and 24/7 community member and expert support for all active ink! users.
Conclusion: is ink! worth using?
The blockchain industry is rapidly evolving, with hundreds of new projects emerging day by day. Smart contracts form the kernel of blockchain systems’ functioning, giving users an efficient and safe way to complete transactions. Thus, having a variety of efficient, user-friendly tools for smart contract development is essential for advancing this niche and setting up new workable projects.
In this article, we described the most bugging issues that make developing in ink! harder. With the cumbersome macro logic and limited extensibility, the currently present unavailability of separate events identification, and the general absence of typical use case references, mastering ink! may seem a challenge for many. Still, ink! is beautiful, and these issues are merely improvement suggestions, not the things why you should NOT use ink!.
Besides, it would be fair to note that the ink!’s community is active, and developers work hard to make it better day by day. So, coders around the world seeking effective, free, and easily implementable coding solutions may expect to see smart contracts in ink! rise and shine.