Add compile-time check that prevents ink! messages with a `&self` receiver from mutating state via `ink_env`
Overview
Following Rust semantics, ink! messages with a &self receiver (i.e. an immutable self reference receiver) shouldn't be able to mutate state/storage because the storage type is the Self type. However, this is currently possible by calling functions from ink_env directly (e.g. ink_env::set_contract_storage, ink_env::transfer e.t.c).
See https://github.com/use-ink/ink/issues/1969 for additional details.
Motivation
- Consistency with Rust semantics makes ink! contracts easier to formally verify with Rust verification tools
- Enforcement of claimed guarantees and consistency with Rust semantics makes ink! contracts easier to audit
- Producing verifiable builds of ink! contracts with messages which mutate state, presented as read-only operations in verifiable ink! ABI metadata, and thus in potentially user facing tools (like wallets and explorers) likely has security implications at the application level
Design
- Require ink! build tools (i.e. cargo-contract) to set a rustc
cfgsetting/flag (i.e.--cfg 'ink_check="msg_mut"') that essentially advertises there ability check/enforce (at compile-time) that ink! messages with&selfreceivers don't call anyink_envfunctions that can mutate state (i.e. they'll emit an error otherwise during thecheckorbuildprocess) - Update the ink proc-macro to emit an compile-time error if (and only if) it detects a message with a
&selfreceiver and the aforementionedcfgsetting is NOT enabled/set (i.e.#[cfg(not(ink_check = "msg_mut"))]- see also) - Create a MIR check that finds ink! message functions with immutable self reference receivers and emits a compiler error if it finds any calls to any
ink_envfunctions that mutate state/storage/environment (i.e.ink_env::set_contract_storage,ink_env::transfer,ink_env::emit_evente.t.c) - Run the MIR check in
cargo-contractduring "check" and "build" commands for ink! contracts using ink! version >= 6
Additional Considerations
The current implementation of ink! linting infrastructure adds about a ~2x build-time penalty because it re-compiles the ink! contract with a fixed version of the Rust nightly compiler. Potential solutions for reducing this build-time penalty will be explored as part of the fix(es) for this issue.
@davidsemakula While I agree that this is a very important issue, I've taken the liberty of marking it "Optional" instead of "Required" for v6. I think it's an involved issue and we shouldn't block the release on it. Lmk if you disagree!
@cmichi We already gave up on landing this in v6 due to resource constraints, I just forgot to update its tags on the board. So marking it optional is fine.