Request an Audit
Back
  • 10/21/2024

Sway Security Audit Guidelines

TL;DR

In the article at Introduction to the Sway Language Security Audit https://exvul.com/introduction-to-the-sway-language-security-audit/, we briefly outlined several security concerns associated with the Sway language. This piece delves deeper into the new features of Sway and provides guidance for writing secure smart contracts. We will introduce the newly-introduced Predicate in Sway and discuss its security implications. Following that, we will address potential security issues in contract development, focusing on four main aspects: Access Control, Business Logic Operations, and Randomness.

Predicate

Predicates, in Sway, are programs that return a Boolean value and do not have any side effects (they are pure). A predicate address can own assets. The predicate address is generated from the compiled byte code and is the same as the P2SH address used in Bitcoin. Users can seamlessly send assets to the predicate address as they do for any other address. To spend the predicate funds, the user has to provide the original byte code of the predicate together with the predicate data . The predicate data will be used when executing the byte code , and the funds can be transferred if the predicate is validated successfully.

https://docs.fuel.network/docs/fuels-rs/predicates/#predicates

First, let’s understand what a predicate is through several diagrams. The source code of a predicate is compiled to generate its corresponding binary code. Subsequently, this binary code is hashed to obtain the address of the predicate.

The Predicate Address has the capability to receive fund transfers, similar to an Externally Owned Account (EOA). It can accept fund transfers from both EOAs and Contract Accounts (CAs).

If users want to unlock funds located at a predicate address, they must attach the corresponding predicate binary code in the transaction and provide input that makes the predicate return True.

Deadlock

In smart contracts, it is common to include a withdrawal function that allows for the safe retrieval of funds in emergency situations. However, when it comes to predicate logic, this straightforward withdrawal operation becomes impractical. It’s crucial to understand that predicates are not contract entities themselves and their code is not stored on the blockchain. The only way predicates can unlock funds is through the submission of code and input data that meet specific conditions. The hash of this code must match the predicate’s address, and the input data must cause the code to return true. Ensuring the accuracy and robustness of predicate logic is therefore extremely important. This also means that the generation and compilation process of predicate addresses is closely tied, as the predicate address is calculated based on the binary code of the predicate. Different versions of compilers may generate distinct predicate addresses even from the same source code. To avoid such discrepancies, it is recommended to use the fuelup tool and the accompanying fuel-toolchain.toml file to determine the toolchain, assuring consistency in the compilation outcomes. This measure is critical to prevent security vulnerabilities or financial losses due to version differences.

Replay

In the Fuel ecosystem, even though the global state is not stored directly in the predicate code, the predicate code and its inputs are public and visible through transactions. Moreover, as predicates function as pure functions, their ability to read is limited to their transaction inputs, and they cannot access elements like nonce or timestamps that might verify the validity of a user’s input. This means that anyone could potentially use the same predicate code and input to access funds held at a specific predicate address.

Therefore, when designing and utilizing predicates, we should maximize their ability to read transaction contents and perform meticulous and strict input validation. This includes specifying the address where funds are received and ensuring the verification of the sender’s identity, thereby enhancing the security and effectiveness of transactions.

Phishing Attack

Binary-coded predicates are not stored on the blockchain. However, like an account, a predicate address can receive funds. This situation prevents users from assessing the security of a predicate based simply on its address. There’s a risk that hackers could provide fake predicate codes, thereby deceiving users.

Access Control

Ownership Check

The admin functions in contracts require a robust authentication mechanism, and Sway provides the SRC5 standard for this purpose, which aims to help developers ensure secure contract management. Additionally, a related authentication library is available in the sway-libs repository to facilitate the management of permissions. Please note that as of today (October 21, 2024), the SRC-5 standard does not yet support functionality for transferring ownership. The sway-libs ownership library only allows for single-step ownership transfers. Hence, developers must exercise extreme caution while using sway-libs for transferring ownership to avoid irreversible consequences due to accidental operations.

Function visibility

Principle of Least Privilege:
Unlike Solidity, which uses declarations like public , private , and pure , all functions within an impl in Sway are public by default. If you wish to define a function for a contract while keeping it private so that only your contract can call it, you should define it outside of the impl and invoke it from within the contract. For more details, please refer to the documentation.

Business logic

Signed integers

Sway was only designed to support unsigned integer types. As a result, there is a library for signed integers in Sway libs (link).Using these libraries requires extra caution. For example, in I8, due to limitations of the FuelVM, the implementation of signed integers in Sway Lib involves adding 128 to a uint. In the i8.sw library, there is a function called from_uint with the following implementation:

pub fn from_uint(underlying: u8) -> Self {
    Self { underlying }
}

This could mislead developers into thinking that from_uint is a function for assigning values to i8, causing positive integers to turn into negatives. The correct functions to call would be try_from and neg_try_from .

Front-running

Front-running is the practice of executing transactions in advance by exploiting knowledge of others’ future actions. This strategy allows front-runners to gain an unfair advantage by predicting and profiting from the outcome of these upcoming transactions.

Front-running can compromise the fairness and integrity of decentralized applications, leading to potential financial losses, unfair advantages in gaming, manipulation of market prices, and an overall erosion of trust in the platform.

Price Oracle Manipulation

In decentralized finance applications, price oracles that derive transaction prices based on the liquidity ratios of token pairs are at risk of being manipulated. This vulnerability stems from the ability of market participants with large token holdings to alter these ratios. By strategically adjusting their holdings, these participants can influence the liquidity ratio, thus affecting the price set by the oracle and potentially leading to the depletion of the liquidity pool. It is advisable to employ multiple oracles for price determination to mitigate this risk.

Operations

Pausing functionality

Contracts should be capable of halting operations efficiently. For immutable contracts, integrating a pause feature is essential. Contracts that can be upgraded may implement pausing through specific smart contract functions or through updates to the contract itself. It is crucial for teams to have automated processes in place for quick and effective activation of this feature.

Lacking a mechanism to pause operations can leave a contract exposed to vulnerabilities for an extended period, which could lead to substantial losses. An effective pause feature enables a swift response to security threats, bugs, or other urgent problems, reducing the chances of exploitation and safeguarding both user assets and the integrity of the contract.

Publishing key management

Using the same account for testnet and mainnet poses a security risk, as testnet private keys, often stored in less secure environments (ex. laptops), can be more easily exposed or leaked. An attacker that can obtain the private key for the testnet smart contract would be able to upgrade the mainnet one.

Randomness

In the application of blockchain and smart contracts, the generation and application of random numbers constitute a key link, particularly in cases involving games, financial derivatives, and any scenarios that require fair randomness. However, the open and immutable nature of blockchain presents challenges to securely and reliably generate random numbers within smart contracts. We propose:

Avoid using blockchain’s own data as a random source: Although seemingly random, blockchain data such as block hashes, transaction counts and timestamps – can be manipulated by miners. Using this data as a source of randomness may lead to the randomness being influenced by attackers, manipulating the result of smart contracts.

Utilize the pre-commitment mechanism: Implementing a pre-commitment and disclosure mechanism in smart contracts is also a viable strategy. Participants initially submit a hash of their data (pre-commitment) and reveal their original data at a later stage. The calculation of random numbers is only carried out once all the necessary data has been disclosed.

Mix random sources: Combining multiple random sources can enhance the security of random number generation. This can be achieved by integrating various different on-chain or off-chain random sources, such as a combination of user input, third-party service data, and blockchain data processing results.

Time-locks and delay mechanisms: In certain application scenarios, introducing time-locks or delayed revelation of random numbers can enhance security as it increases the cost and complexity of manipulating random numbers.