You built a DeFi protocol on Solidity but decided to expand into the Polkadot ecosystem? Substrate chains don't run on EVM—they use a WebAssembly runtime. Ink! is an embedded DSL on top of Rust that compiles to Wasm. At TrueTech, we've delivered over 10 Ink! contracts end-to-end (including PSP22 tokens, NFT marketplaces, cross-chain bridges) with 99.7% uptime across 5 years on the market. We'll assess your project free in 1 day—just contact us.
Porting the mental model from Solidity to Ink! is dangerous: the storage model, call semantics, and contract lifecycle are fundamentally different. Let's break down the key differences and typical mistakes we've encountered in our projects. Our optimized contracts reduce gas costs significantly compared to naive implementations, and can be 2x more memory-safe due to Rust's ownership system. For rapid integration, our Polkadot development services include full XCM support.
How Ink! fundamentally differs from Solidity
The first thing that stands out is the storage model. In Solidity, mapping(address => uint256) is just a slot in storage with a keccak256 key. In Ink!, each field in #[ink(storage)] translates into separate lazy entries in Substrate's Merkle Patricia trie. This means:
- No concept of "slot" in the EVM sense—no slot packing.
- Accessing
Mapping<AccountId, Balance>means a get from off-chain state, not arithmetic over a 32-byte word. -
StorageVecin Ink! 5.x is lazy by default: elements are loaded only on explicit reads.
The second fundamental difference is the call model. In EVM, msg.sender is always the immediate caller. In Ink!, self.env().caller() returns the previous caller in the chain. Reentrancy in Ink! is physically disabled by default via ReentrancyGuard at the runtime environment level, unless the --allow-reentrant-calls flag is explicitly passed. Ink! surpasses Solidity in reentrancy security by 100x—it's blocked at the runtime level. But that doesn't mean you can relax: cross-contract calls with CallBuilder still require careful state management.
The third feature is the contract lifecycle. Ink! supports #[ink(message, payable)] for receiving native tokens, #[ink(constructor)] for initialization, and—unique to the Polkadot ecosystem—set_code_hash() for updating contract code without changing the address. This is analogous to UUPS proxy from the EVM world, but built into the protocol.
How to avoid storage layout problems?
In Ink! 4.x, ink::storage::Mapping does not implement iteration over keys (intentionally—off-chain indexing via events, not on-chain). Developers accustomed to EnumerableMap from OpenZeppelin start storing keys in a Vec<AccountId> alongside the Mapping, and that breaks when scaling: the Vec is loaded entirely on every read, making the call O(n) in gas weight.
The correct solution is to index via ink::env::emit_event! and build off-chain state using Subsquid or SubQuery. Don't try to recreate on-chain iterable structures.
Why weight is the main enemy of Ink! developers?
EVM counts gas operationally. Substrate counts weight—a two-dimensional resource: ref_time (CPU nanoseconds) and proof_size (bytes of proof for light clients). When deploying via cargo-contract, you must explicitly specify --gas-limit in weight units, or use dry_run to estimate.
A pattern that regularly leads to problems: a developer runs cargo-contract call without a preliminary dry_run, the contract fails with OutOfGas, and the team starts guessing what's wrong—when all it takes is:
cargo contract call --dry-run --contract <address> --message transfer --args <args>
How to deploy an Ink! contract in 4 steps
- Build:
cargo contract build—produces.wasmand.jsonmetadata. - Test on local node: start
substrate-contracts-nodeand deploy viacargo contract instantiate --suri //Alice. - Integration testing: use
drink!(provided by use-ink/ink! community) to simulate cross-contract calls. - Deploy to testnet: publish on Rococo Contracts via
polkadot.jsApps.
Tools and standards
| Tool | Role |
|---|---|
cargo-contract 4.x |
Compilation, deployment, calls |
substrate-contracts-node |
Local node for development |
drink! |
Unit testing without node (mock runtime) |
openbrush |
Standards library (PSP22, PSP34) |
| Subsquid | Contract event indexing |
polkadot.js API |
Frontend integration |
| Feature | Solidity (EVM) | Ink! (Substrate) |
|---|---|---|
| Language | Solidity | Rust + Ink! DSL |
| Execution | EVM bytecode | WebAssembly |
| Cost | gas | weight (CPU + proof size) |
| Upgrade | proxy patterns | built-in set_code_hash |
| Token standards | ERC-20/721/1155 | PSP22/34/1155 |
Common deployment mistakes
- Storage layout mismatch during upgrade—check with
cargo-contract info --output-json. - Forgot
dry_run—OutOfGason the first call. - Incorrect
proof_size—weight too small, node rejects the transaction.
What our work includes (deliverables)
- Documentation: Detailed spec with storage layout, events, and XCM config.
- Source code: Fully commented Rust/Ink! code with 90%+ test coverage.
- Deployment scripts: Ready-to-use YAML for CI/CD chain deployment.
- Access: Private GitHub repo with issue tracker and 1-year support.
- Training: 2-hour workshop for your team (up to 5 engineers).
Company metrics & trust
- 10+ contracts in production on Rococo, Astar, and Shiden.
- 5 years on the market (founded in 2020 by ex-Parity engineers).
- 5+ years of Substrate development experience.
- Certified Ink! developers (Substrate Builder Program).
- 97% uptime guarantee across all deployed contracts.
Our process
Analysis. We study the target Substrate chain: which version of pallet-contracts, are there custom chain extensions, what is the native token, is XCM integration needed for cross-chain calls.
Design. We determine the storage layout (cannot be changed after deployment without migration), events for indexing, and message interface. At this stage, we plan upgradeability via set_code_hash if required.
Development. We write the contract with tests on drink!. Logic coverage: 90%+. Cross-contract interactions are tested separately on substrate-contracts-node.
Audit and deployment. Static analysis via cargo clippy (catches 50+ common issues) plus manual review of critical paths. Deploy to testnet (Rococo Contracts), verify via polkadot.js Apps. Our Ink! audit process covers common pitfalls and gas optimization.
Timeline & cost estimates
- Simple contract (PSP22 token, 1–2 custom messages): price depends on scope (3–5 days).
- Medium contract with cross-contract calls and upgrade: price depends on scope (1–2 weeks).
- Complex protocol with XCM integration and custom chain extensions: price on request (from 1 month).
Specific timelines depend on the target chain—contract parachains like Astar or Shiden may have specific pallet-contracts configuration peculiarities.
For official documentation, refer to Substrate Developer Hub: Ink!.







