Comment on page
ink! Developers Security Guideline
This guide aims at supporting ink! developers who want to deploy their project on the Aleph Zero blockchain. It has been developed as part of the partnership between Aleph Zero and Kudelski Security.
- Set the compiler flags debug-assertion and overflow-checks to true. These can be set in Cargo.toml for profile.release.
- Use rustfmt formatter. Customize your guidelines by creating a rustfmt.toml in the root of you project and perform the following commands:cargo +nightly fmt \-- \--checkcargo +nightly fmt
- 1.Install Rustup. If you already have Rustup installed, update to ensure you have the latest Rustup and compiler:
rustup update- 2.Install Clippy
rustup component add clippy- 3.Run Clippy
cargo clippy- 4.If necessary, fix Clippy suggestions automatically
cargo clippy --fix- 1.Install rustfix
cargo add rustfixcargo install cargo-fixcargo fix- 2.Open the rust files to manually verify the fixes.
- Use cargo audit which audits your dependencies for crates with security vulnerabilities reported to the RustSec Advisory Database.
- 1.Install cargo-audit
cargo install cargo-audit- 2.Run cargo audit
cargo audit - 1.Install cargo-outdated
cargo install --locked cargo-outdated- 2.Run cargo outdated
cargo outdated- cargo update
Ensure that the design documents of your smart contract contain the following components:
- Architecture diagrams with all global state variables.
- Code documentation:
- We strongly ncourage developers to update the document as written in the code.
- We also encourage to include a set up guideline and simple use case examples.
While designing the smart contract, a threat assessment needs to be performed with the following five steps. A threat is an element which can hurt, harm, tamper with, or attack the smart contract.
- 1.Context establishment
- How will the project be used?
- Who is the target audience?
- 2.Threat assessment
- This includes a list of all assets used in the project and their associated risks or threats
- 3.Threat analysis & evaluation
- Assets are classified by their risks/threats and their likelyhood.
- 4.Mitigation treatment
- What can be done to mitigate these risks/threats?
- 5.Risk and control monitoring
- Which operation can be done to conrol these risks/threats?
- Rust API Guidelines Checklist is a set of recommendations on how to design and present APIs for the Rust programming language.
- Are the following elements using UpperCamelCase?
- Types
- Traits
- Enum variants
- Are the following elements using snake_case?
- Functions
- Macros
- Variables
- Modules
- Are the following elements using SCREAMING_SNAKE_CASE?
- Statics
- Constants
- Is the following element using lowercase?
- Lifetimes
[ ] If one of the above boxes has not been checked, we strongly encourage to change your code to follow the recommended convention.
-
- [ ] Ad-hoc conversions follow as_, to_, into_ conventions ([C-CONV](https://rust-lang.github.io/api-guidelines/naming.html#c-conv))
-
- [ ] A malicious contract calls back into the calling contract before the first invocation of the function is finished.
- Make sure all internal state changes are performed before the call is executed.
- Use a reentrancy lock function if available.
- https://github.com/crytic/not-so-smart-contracts/tree/master/reentrancy
- https://swcregistry.io/docs/SWC-107
-
- [ ] There is a gap between the creation of a transaction and the moment it is accepted in the blockchain. Therefore, an attacker can take advantage of this gap to put a contract in a state that advantages them.
- Be aware that all transactions may be front-run.
- https://github.com/crytic/not-so-smart-contracts/tree/master/race_condition
-
- [ ] Contract ensures that each provided address is not zero or one of the default test accounts (e.g. Bob, Alice) because zero address public key has a known private key in the sr25519 and ed25519.
- Use the ink! v4.4.0-rc or higher version where the default implementation for AccountIds by zero has been removed.
- Tests against testnet allow the use of Zero address, but must be specified.
- https://substrate.stackexchange.com/questions/982/why-does-the-all-0-public-key-have-a-known-private-key-in-sr25519-and-ed25519
-
- [ ] If a function or asset should be available only to a restricted set of entities, you need to verify that the call has been signed by the appropriate entity.
- https://github.com/project-serum/sealevel-attacks/tree/master/programs/0-signer-authorization
- https://blog.neodyme.io/posts/solana_common_pitfalls/#missing-signer-check
-
- [ ] Your contract should trust accounts owned by itself. Check the owner and return an object of a trusted type.
- https://github.com/project-serum/sealevel-attacks/tree/master/programs/2-owner-checks
- https://blog.neodyme.io/posts/solana_common_pitfalls/#missing-ownership-check
-
- [ ] Be aware that an attacker can stall contract execution by failing in a strategic way. In particular, contracts that bulk perform transactions or updates using a loop can be DoS'd if a call to another contract or transfer fails during the loop.
- If iterating over a dynamically sized data structure, be able to handle the case where the function takes multiple blocks to execute. One strategy for this is storing iterator in a private variable and using while loop that exists when gas drops below certain threshold.
- https://github.com/crytic/not-so-smart-contracts/tree/master/denial_of_service
-
- [ ] If the contract allows for removal of items from storage, these instructions should be properly authorized
- https://swcregistry.io/docs/SWC-106 (Solidity)
- https://github.com/Supercolony-net/openbrush-contracts/blob/main/examples/psp22_extensions/burnable/lib.rs (Burnable PSP22 contract).
-
- [ ] Use delegator call with caution and make sure to never call into untrusted contracts. If the target address is derived from user inputs, ensure to check it against a whitelist of trusted contracts.
- https://swcregistry.io/docs/SWC-112
- https://use.ink/basics/cross-contract-calling
-
- [ ] Authorization checks by `Self.env().caller()` should validate the contract-only controllable values.
- Ensure that the use of
Self.env().caller()
does not allow for manipulating sensitive values. - https://swcregistry.io/docs/SWC-115 (Solidity)
- https://blog.neodyme.io/posts/solana_common_pitfalls/#solana-account-confusions (Solana)
[ ] Signature Malleability: Valid signatures might be created by an attacker replaying previously signed messages.
-
- [ ] Ensure that a signature is never included into a signed message hash to check if previously messages have been processed by the contract.
- https://swcregistry.io/docs/SWC-117
- https://eklitzke.org/bitcoin-transaction-malleability
-
- [ ] Through the *#[ink::trait_definition]* [ ] proc. macro, your own trait definitions are implementable by ink! smart contracts. This allows to define shared smart contract interfaces to different concrete implementations.
- When defining shared smart contracts, you should carefully specify the trait definitions in the correct order.
- https://use.ink/basics/trait-definitions
- https://swcregistry.io/docs/SWC-125
-
- [ ] In the case of a relayer contract, the user who executes the transaction, the 'forwarder', can effectively censor transactions by using just enough gas to execute the transaction, but not enough for the sub-call to succeed.
- This attack is a form of "griefing": It doesn't directly benefit the attacker, but causes grief for the victim.
- To prevent this attack, only allow trusted users to relay transactions or require that the forwarder provides enough gas.
- https://swcregistry.io/docs/SWC-126
- https://consensys.github.io/smart-contract-best-practices/known_attacks/#insufficient-gas-griefing
- https://ethereum.stackexchange.com/questions/73261/griefing-attacks-are-they-profitable-for-the-attacker
-
- [ ] Values hardcoded in the contract lead to unexpected behavior.
- Unexpected balance reflects some token balance or address hard-coded into a contract
- https://swcregistry.io/docs/SWC-134 (Message call with hardcoded gas limit)
- https://swcregistry.io/docs/SWC-132 (Solidity)
-
- [ ] Verify that there is no bad randomness: On-chain randomness may be a weak because it can be manipulated by users.
- Use a random number generator whose randomness has been verified.
- Use external sources of randomness via oracles if trusted oracles are available. Note that it may be reasonable to use multiple oracles, in particular for a seed of the random sequence.
- Use the up-to-date API to get a random number.
- https://github.com/crytic/not-so-smart-contracts/tree/master/bad_randomness
- https://swcregistry.io/docs/SWC-120
-
- [ ] If `doverflow-checks = false` in `Cargo.tolm` file please wrap arithmetic operations with safe math functions or validate all arithmetic to prevent overflows. * https://github.com/crytic/not-so-smart-contracts/tree/master/integer_overflow * https://swcregistry.io/docs/SWC-101 * https://medium.com/coinmonks/understanding-arithmetic-overflow-underflows-in-rust-and-solana-smart-contracts-9f3c9802dc45
When it comes to memory management, the following checks need to be done:
- Uninitialized memory must not be used.
- Zero out memory of sensitive data after use.
- The forget function of std::mem (core::mem) must not be used.
- The code must not leak memory or resource in particular via Box::leak.
- Iterators are used rather than explicit indexing.
- If indexing is necessary, explicitly check for correctness.
When it comes to memory management, the following checks need to be done:
- The ? operator can be used to improve readability of code.
- The try! macro should not be used.
- The Result enum for explicit error handling is used instead of calling panic.
- Try to avoid using crates containing functions or instructions that can fail and cause the code to panic.
-
- [ ] A different type of interfaces are implemented, causing a different method ID to be created. For example, `Alice.set(uint)` takes an `uint` in Bob.rs but `Alice.set(int)` a `int` in Alice.rs. The two interfaces will produce two differents method IDs. As a result, Bob can call the fallback function of Alice rather than of `set`.
- https://github.com/crytic/not-so-smart-contracts/tree/master/incorrect_interface
-
- [ ] A constructor function is named correctly so that it does not cause to end up in the runtime bytecode instead of being a constructor.
- A constructor is responsible for bootstrapping the initial contract state into the storage.
- Use
#[ink(constructor)]
constructor instead of a named constructor. - https://github.com/crytic/not-so-smart-contracts/tree/master/wrong_constructor_name (Solidity)
- https://swcregistry.io/docs/SWC-118 (Solidity)
- https://use.ink/3.x/macros-attributes/constructor
-
- [ ] Initialized local storage variables, otherwise they can point to unexpected storage locations in the contract, which can lead to intentional or unintentional vulnerabilities.
- Use
ink_storage::Mapping
which maps key-value pairs directly into contract storage. - https://swcregistry.io/docs/SWC-109
- https://use.ink/datastructures/mapping
-
- [ ] The assert function should only be used to test for internal errors, and to check invariants.
- Consider whether the condition checked in the
assert!
is actually an invariant. If not, replace theassert!
statement with arequire!
statement. - https://swcregistry.io/docs/SWC-110 (Solidity)
- https://use.ink/ink-vs-solidity/#assert
-
- [ ] `if ... { return Err(Error::SomeError) }` should be used for `require` or `revert`. When a `Result::Err` is returned in ink!, then all state is reverted.
- https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/assert-require-revert/ (Solidity)
- https://use.ink/ink-vs-solidity#require-and-revert
-
- [ ] Be cautious when you expect to have large arrays that grow over time. Actions that require looping across the entire data structure should be avoided.
- If you absolutely must loop over an array of unknown size, then you should plan for it to potentially take multiple blocks, and therefore require multiple transactions.
- https://swcregistry.io/docs/SWC-128
-
- [ ] Be aware that an account can be vulnerable to program re-initialization.
- Check whether the contract is already initialized from other program before executing the initialization function.
- https://github.com/project-serum/sealevel-attacks/tree/master/programs/4-initialization
-
- [ ] An attacker who is running a node can tell which transactions are going to occur before they are finalized. A race condition vulnerability occurs when code depends on the order of the transactions submitted to it.
- https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit# (ERC20)
- https://swcregistry.io/docs/SWC-114
- https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 (Ethereum)
- https://medium.com/coinmonks/solidity-transaction-ordering-attacks-1193a014884e (Solidity)
This part aims to help preparing for a security audit which is necessary in order to achieve the best security possible. It is important to see an audit as a partnership between you and the company performing the audit. Therefore, it is important that you prepare some documents to the good functionning of audit.
Are the following elements ready?
-
- [ ] Explaination of the purpose of the smart contract
- Documentation and explanation of all smart contract's functions and variables
- Examples and use cases:
- Machine setup, installation, deployment and testing instructions:
- Example of helpful documents
- architecture diagrams,
- threat models,
- academic papers,
- whitepapers,
- blog posts,
- or any other piece of documentation relevant to the project.
- High-risk findings have been escalated immediately via a secure channel.
- Communication with audit team are frequent enough.
- Questions from the audit team have been answered.
- All findings have been corrected and re-reviewed.
- Keep monitoring new security attacks and evaluate your contracts accordingly.
- Apply remedies to the smart contracts immediately if new vulnerabilities are discovered.
- Contract needs to be upgratable.
- Run automated code analysis tools whenever new commits are added to the repository.
- Pause the smart contract when an unusual happen.
- Contract needs to have a pause option.
- Consider re-auditing your smart contracts if they have been significantly updated.
- Feel free to contact security partners for evaluation and scoping.