ink! Smart Contract Language

repository·master·Indexed 23 days ago

https://github.com/use-ink/ink

A Rust-based smart contract language for the Polkadot SDK. It provides tools for writing safe and efficient contracts, including a platform-independent metadata system, storage management via the #[ink::storage_item] attribute, and integration with ERC-20 asset precompiles. The documentation covers contract instantiation, storage key configuration (ManualKey, AutoKey), and migration from v3 to v4 storage models.

Tokens
71.7K
Snippets
157
Records
425
Agent score
79%

What's inside ink!

  1. Overview of ink! smart contract development

    master

    ink! is a Rust-based smart contract language designed for the Polkadot SDK. It allows developers to write high-performance, secure smart contracts using Rust's type system and safety features.

    Note: As of January 2026, ink! is no longer actively maintained or developed. New issues and pull requests are locked.

  2. Understand Mandatory ink! linting rules

    master
    Mandatory ink! linting rules are dylint-based lints that are integrated directly into the ink! contract build process. Unlike standard warnings, these lints are designed to generate custom compilation errors, ensuring that certain contract patterns or safety requirements are strictly enforced during development.
  3. Overview of ink! crate architecture

    master

    ink! is a collection of crates designed for no_std environments (except for metadata and engine). The core components include:

    • ink: The central umbrella crate for the ink! eDSL.
    • allocator: A simple bump allocator for dynamic memory. It never frees space to minimize gas costs and complexity, as memory is reset for every contract call.
    • env: Provides environmental functions (e.g., caller info, self-termination) and the connection to pallet-revive for storage and pallet interaction.
    • metadata: Describes the contract interface, types, and storage layout in a platform-agnostic way.
    • prelude: Provides access to standard library-like functionality (e.g., vec, string) in a no_std context.
    • storage: Collections available for contract developers to use in smart contract storage.
    • engine: An off-chain testing engine for simulating blockchain environments.
    • e2e: An end-to-end testing framework requiring a Polkadot SDK node with pallet-revive.
  4. What is ink! Metadata?

    master

    ink! metadata is a platform-independent description of a contract's properties. It allows external tooling to understand the contract's interface and capabilities.

    Note that the metadata version is independent of the ink! language version. For example, version 4 of the metadata is used in ink! v5.

  5. Understand performance and debugging implications of `pallet-revive`

    master

    When using ink! on pallet-revive, developers should be aware of several architectural constraints inherited from the fork of pallet-contracts. These factors impact performance, data encoding, and error debugging:

    Performance and Encoding

    • Pre-compile Overhead: Many host functions have been migrated to pre-compiles. Calling these functions incurs the performance overhead of a contract-to-contract call rather than a direct host call.
    • ABI Encoding: pallet-revive pre-compiles use a Solidity interface instead of SCALE encoding. ink! must re-encode arguments into the Solidity ABI format and decode responses, which adds computational overhead.

    Data Types and Events

    • Balance Types: Developers must handle two different types for contract balances: the generic Balance (defined in chain configuration) and the Ethereum-native U256.
    • Event Availability: Most smart-contract-specific events (e.g., Called, ContractCodeUpdated, CodeStored, CodeRemoved, Terminated, DelegateCalled, StorageDepositTransferredAndHeld, StorageDepositTransferredAndReleased) have been removed. The Instantiated event remains available.

    Debugging and Errors

    • Error Semantics: Due to the shift to pre-compiles, some semantic error information is lost. For example, a call that fails due to insufficient Gas (which previously returned OutOfGas) may now return ContractTrapped. This can make it more difficult to distinguish between logic errors and resource exhaustion.
  6. How ink! communicates with pallet-revive

    master

    ink! contracts are executed by pallet-revive within a PolkaVM environment. To move data between the contract and the pallet without expensive heap allocations, ink! uses a static buffer and pointer arithmetic.

    Communication follows a specific pattern:

    1. To the pallet: Values are SCALE-encoded on the ink! side and passed as a byte slice.
    2. From the pallet: Values are SCALE-decoded on the ink! side to convert them into usable Rust types for the developer.
  7. Implement the Delegator pattern with `delegate_call`

    master

    The Delegator pattern uses the low-level host function delegate_call to allow a contract to delegate execution to on-chain uploaded code. Unlike a traditional cross-contract call, delegate_call delegates to the code rather than the contract address, meaning the execution occurs within the context of the caller's storage.

    Storage Compatibility and CallFlags

    Because the delegated code operates on the caller's storage, you must manage storage layouts carefully:

    • Layout-full storage: If the delegated code modifies any field that is NOT a Lazy or Mapping type, you must specify the CallFlags::TAIL_CALL flag and ensure the storage layouts match exactly.
    • Lazy or Mapping fields: If the delegated code only modifies Lazy or Mapping fields, the keys must be identical. In this case, CallFlags::TAIL_CALL is optional because these types interact with storage directly without loading/flushing the entire storage state.
    • Layoutless storage: If your storage consists only of Lazy and Mapping fields, the order of fields and the overall layout do not need to match.
  8. How to handle generic storage keys in contracts

    master

    When a struct uses a generic KEY: StorageKey, the #[ink::storage_item] macro sets the ParentKey generic value to KEY, effectively concatenating them. This allows you to reuse the same type definition in different contexts with different storage keys.

    Example usage in a contract:

    #[ink(storage)]
    struct MyContract {
        my_struct: MyStruct<ManualKey<123>>,
    }

    Important: Avoid direct assignment of default instances. Because every type is unique after code generation (due to its specific storage key), assigning a value like Balances::default() to a field that expects a specific ManualKey will cause a type mismatch error.

    Incorrect:

    instance.balances = Balances::<ManualKey<123>>::default(); // Error

    Correct: Use Default::default() to allow the compiler to generate the correct type with the expected storage key:

    instance.balances = Default::default();
    #[ink(storage)]
    struct MyContract {
        my_struct: MyStruct<ManualKey<123>>,
    }
    
    // Inside a constructor or method:
    instance.balances = Default::default();
  9. Migrate storage during contract upgrades

    master

    When an upgrade introduces a new storage layout, you can use an intermediate migration contract to perform the migration. This prevents the contract from becoming unusable due to storage mismatch.

    Workflow:

    1. Upload a migration contract: This contract must include a migrate message designed to perform the storage migration.
    2. Update code hash: Use set_code_hash() to point the contract address to the migration contract.
    3. Upload new version: Upload the upgraded version of your original contract.
    4. Execute migration: Call the migrate message on the migration contract, passing the code hash of the new upgraded contract as an argument.

    Critical Requirement: Steps 2 through 4 must be executed as a single message. If the migration is not completed in the same transaction that sets the code hash, the contract will fail to load the migrated storage and will become uncallable.

  10. Inspect generated Rust code with cargo-expand

    master

    The ink crate uses procedural macros (ink_macro) to transform code into an Intermediate Representation (ink_ir), which is then turned into Rust code by ink_codegen. To see the actual Rust code generated for your contract, you can use the cargo-expand tool.

    Note: You must specify the RISC-V target for the expansion to be accurate.

    cd ink/integration-tests/public/flipper/
    cargo expand --no-default-features --target riscv64gc-unknown-none-elf
  11. Use extra ink! linting rules for secure coding

    master
    The ink_linting package provides optional linting rules designed to check for secure coding styles in smart contracts. These rules highlight potential security issues and are intended to help contract developers improve the security properties of their projects.