SWC Registry
Smart Contract Weakness Classification
The following table contains an overview of the SWC Cases taken in consideration while developing the smart contracts.
Function Default Visibility
Functions that do not have a function visibility type specified are public by default. This can lead to a vulnerability if a developer forgot to set the visibility and a malicious user is able to make unauthorized or unintended state changes.
It was an issue in v0.4.X solidity versions but after v0.5.X solidity makes it compulsory to define visibility. (Currently using solidity v0.8.2)
Integer Overflow and Underflow
An overflow / underflow happens when an arithmetic operation reaches the maximum or minimum size of a type. An integer overflow occurs when an arithmetic operation attempts to create a numeric value that is outside of the range that can be represented with a given number of bits – either larger than the maximum or lower than the minimum representable value.
The issue persists in versions before v0.8.X. After v0.8.X, underflow and overflow of integers are by default handled by solidity.
Outdated Compiler Version
Using an outdated compiler version can be problematic especially if there are publicly disclosed bugs and issues that affect the current compiler version.
There are only two known vulnerabilities after v0.8.X. 1. Use of keccak. (Not used in the current contracts) 2. Use of abi.decode. (Not used in the current contracts)
Floating Pragma
Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.
Using a fixed compiler version v0.8.2.
Unchecked Call Return Value
The return value of a message call is not checked. Execution will resume even if the called contract throws an exception. If the call fails accidentally or an attacker forces the call to fail, this may cause unexpected behaviour in the subsequent program logic.
How to handle : If you choose to use low-level call methods, make sure to handle the possibility that the call will fail by checking the return value. Not using low-level call methods instead using Abstraction to call external contracts.
Unprotected Ether Withdrawal
Due to missing or insufficient access controls, malicious parties can withdraw some or all Ether from the contract account. This bug is sometimes caused by unintentionally exposing initialization functions. By wrongly naming a function intended to be a constructor, the constructor code ends up in the runtime byte code and can be called by anyone to re-initialize the contract.
Use of require & assert statements or modifier functions needed in case of accessing functions.
Unprotected SELFDESTRUCT Instruction
Due to missing or insufficient access controls, malicious parties can self-destruct the contract.
Remove self destruct unless absolutely needed.
One of the major dangers of calling external contracts is that they can take over the control flow. In the reentrancy attack (a.k.a. recursive call attack), a malicious contract calls back into the calling contract before the first invocation of the function is finished. This may cause the different invocations of the function to interact in undesirable ways.
The best practices to avoid Reentrancy weaknesses are: 1. Make sure all internal state changes are performed before the call is executed. This is known as the Checks-Effects-Interactions pattern. 2. Use a Reentrancy Lock (i.e. OpenZeppelin's ReentrancyGuard).
State Variable Default Visibility
Labeling the visibility explicitly makes it easier to catch incorrect assumptions about who can access the variable.
Making sure all variables are marked with their state.
Uninitialized Storage Pointer
Uninitialized local storage variables can point to unexpected storage locations in the contract, which can lead to intentional or unintentional vulnerabilities.
As of compiler version v0.5.0 and higher this issue has been systematically resolved as contracts with uninitialised storage pointers do no longer compile.
Assert Violation
The Solidity assert() function is meant to assert invariants. Properly functioning code should never reach a failing assert statement. A reachable assertion can mean one of two things: 1. A bug exists in the contract that allows it to enter an invalid state. 2. The assert statement is used incorrectly, e.g. to validate inputs.
Using assert() to handle invariants or strange situations in the contract.
Use of Deprecated Solidity Functions
Several functions and operators in Solidity are deprecated. Using them leads to reduced code quality. With new major versions of the Solidity compiler, deprecated functions and operators may result in side effects and compile errors.
Avoid use of the deprecated functions, use the alternatives.
Delegatecall to Untrusted Callee
delegatecall allows a smart contract to dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract. Calling into untrusted contracts is very dangerous, as the code at the target address can change any storage values of the caller and has full control over the caller's balance.
Handling all the scenarios w.r.t to the delegatecall being initialized with the smart contracts.
DoS with Failed Call
External calls can fail accidentally or deliberately, which can cause a DoS condition in the contract. To minimize the damage caused by such failures, it is better to isolate each external call into its own transaction that can be initiated by the recipient of the call. This is especially relevant for payments, where it is better to let users withdraw funds rather than push funds to them automatically (this also reduces the chance of problems with the gas limit).
Implementing the logic in such a way that the external calls if they fail, they fail gracefully, i.e. without having a major impact on the functionality of the contract.
Transaction Order Dependence
Execution of multiple functions using a shared state variable can lead to concurrency issues & with Improper Synchronization, a Race Condition.
A possible way to remedy for race conditions in submission of information in exchange for a reward is called a commit reveal hash scheme.
Authorization through tx.origin
tx.origin is a global variable in Solidity which returns the address of the account that sent the transaction. Using the variable for authorization could make a contract vulnerable if an authorized account calls into a malicious contract. A call could be made to the vulnerable contract that passes the authorization check since tx.origin returns the original sender of the transaction which in this case is the authorized account.
tx.origin should not be used for authorization. Use msg.sender instead.
Block values as a proxy for time
Contracts often need access to time values to perform certain types of functionality. Values such as block.timestamp, and block.number can give you a sense of the current time or a time delta, however, they are not safe to use for most purposes.
Developers should write smart contracts with the notion that block values are not precise, and the use of them can lead to unexpected effects. A miner can manipulate block.timestamp value for upto 15 mins, the contract uses DAYs logic hence, it's safe to use it.
Signature Malleability
A system that performs signature verification on contract level might be susceptible to attacks if the signature is part of the signed message hash. Valid signatures could be created by a malicious user to replay previously signed messages.
A signature should never be included into a signed message hash to check if previously messages have been processed by the contract.
Incorrect Constructor Name
Constructors are special functions that are called only once during the contract creation. A function meant to become a constructor becomes a normal, callable function if its name doesn't exactly match the contract name. This behavior sometimes leads to security issues, in particular when smart contract code is re-used with a different name but the name of the constructor function is not changed accordingly.
Before Solidity version v0.4.22, the only way of defining a constructor was to create a function with the same name as the contract class containing it. Fixed after Solidity version v0.4.22.
Shadowing State Variables
Solidity allows for ambiguous naming of state variables when inheritance is used. Shadowing state variables can also occur within a single contract when there are multiple definitions on the contract and function level.
Review storage variable layouts for your contract systems carefully and remove any ambiguities. Always check for compiler warnings as they can flag the issue within a single contract. Also, make sure NOT to override any state variables.
Weak Sources of Randomness from Chain Attributes
Ability to generate random numbers is very helpful in all kinds of applications. Creating a strong enough source of randomness in Ethereum is very challenging.
Remediation : Using commitment scheme, e.g. RANDAO. We don't have any need to generate a random number in our contracts.
Missing protection against Signature Replay Attacks
It is sometimes necessary to perform signature verification in smart contracts to achieve better usability or to save gas cost. A malicious user could attack a contract without such a control and get message hash that was sent by another user processed multiple times.
Since NO Off-chain signature is used in the protocol, signature replay attacks won't be an issue.
Lack of Proper Signature Verification
It is a common pattern for smart contract systems to allow users to sign messages off-chain instead of directly requesting users to do an on-chain transaction because of the flexibility and increased transferability that this provides. This can lead to vulnerabilities especially in scenarios where proxies can be used to relay transactions.
Since NO Off-chain signature is used in the protocol, verification of the same is not requried.
Requirement Violation
The Solidity require() construct is meant to validate external inputs of a function. In most cases, such external inputs are provided by callers, but they may also be returned by callees.
Logical condition should be balanced (Not too strong / weak) to allow all the valid external inputs.Using require to make sure that invalid inputs do not go through.
Write to Arbitrary Storage Location
A smart contract's data is persistently stored at some storage location on the EVM level. The contract is responsible for ensuring that only authorized user or contract accounts may write to sensitive storage locations. If an attacker is able to write to arbitrary storage locations of a contract, the authorization checks may easily be circumvented. This can allow an attacker to corrupt the storage; for instance, by overwriting a field that stores the address of the contract owner.
Make sure that writes to one data structure cannot inadvertently overwrite entries of another data structure.
Incorrect Inheritance Order
Solidity supports multiple inheritance, meaning that one contract can inherit several contracts. Multiple inheritance introduces ambiguity called Diamond Problem: if two or more base contracts define the same function, which one should be called in the child contract?
When inheriting multiple contracts, especially if they have identical functions, a developer should carefully specify inheritance in the correct order. The rule of thumb is to inherit contracts from more general to more specific.
Insufficient Gas Griefing
Insufficient gas griefing attacks can be performed on contracts which accept data and use it in a sub-call on another contract. If the sub-call fails, either the whole transaction is reverted, or execution is continued. 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.
Can be handled using two approaches: 1. Allow only trusted users to relay transactions. 2. Require the forwarder to provide enough gas.
Arbitrary Jump with Function Type Variable
Solidity supports function types. That is, a variable of function type can be assigned with a reference to a function with a matching signature. The function saved to such variable can be called just like a regular function. The problem arises when a user has the ability to arbitrarily change the function type variable and thus execute random code instructions.
The use of assembly should be minimal. A developer should not allow a user to assign arbitrary values to function type variables.
DoS With Block Gas Limit
Programming patterns that are harmless in centralized applications can lead to Denial of Service conditions in smart contracts when the cost of executing a function exceeds the block gas limit. Modifying an array of unknown size, that increases in size over time, can lead to such a Denial of Service condition.
Don't loop over an array of unknown size, either limit the size or potentially take multiple blocks to complete.
Typographical Error
A typographical error can occur for example when the intent of a defined operation is to sum a number to a variable (+=) but it has accidentally been defined in a wrong way (=+), introducing a typo which happens to be a valid operator. Instead of calculating the sum it initializes the variable again.
Using intended operators and validating the scope of the operation to ensure the state of the functions is changed/modified as expected.
Right-To-Left-Override control character (U+202E)
Malicious actors can use the Right-To-Left-Override unicode character to force RTL text rendering and confuse users as to the real intent of a contract.
Very few legitimate uses of the U+202E character. It should not appear in the source code of a smart contract.
Presence of unused variables
Unused variables are allowed in Solidity and they do not pose a direct security issue. It is best practice though to avoid them as they can.
Remove all unused variables from the code base.
Unexpected Ether balance
Contracts can behave erroneously when they strictly assume a specific Ether balance. It is always possible to forcibly send ether to a contract (without triggering its fallback function), using selfdestruct, or by mining to the account. In the worst case scenario this could lead to DOS conditions that might render the contract unusable.
Avoid strict equality checks for the Ether balance in a contract.
Hash Collisions With Multiple Variable Length Arguments
Using abi.encodePacked() with multiple variable length arguments can, in certain situations, lead to a hash collision. Since abi.encodePacked() packs all elements in order regardless of whether they're part of an array, you can move elements between arrays and, so long as all elements are in the same order, it will return the same encoding. In a signature verification situation, an attacker could exploit this by modifying the position of elements in a previous function call to effectively bypass authorization.
When using abi.encodePacked(), it's crucial to ensure that a matching signature cannot be achieved using different parameters. To do so, either do not allow users access to parameters used in abi.encodePacked(), or use fixed length arrays.
Message call with hardcoded gas amount
The transfer() and send() functions forward a fixed amount of 2300 gas. Historically, it has often been recommended to use these functions for value transfers to guard against reentrancy attacks.However, the gas cost of EVM instructions may change significantly during hard forks which may break already deployed contract systems that make fixed assumptions about gas costs.
Developer should avoid using low-level calls & NOT hardcode the gas amount.
Code With No Effects
In Solidity, it's possible to write code that does not produce the intended effects. Currently, the solidity compiler will not return a warning for effect-free code. This can lead to the introduction of "dead" code that does not properly performing an intended action.
Ensure that your contract works as intended. Write unit tests to verify correct behaviour of the code.
Unencrypted Private Data On-Chain
It is a common misconception that private type variables cannot be read. Even if your contract is not published, attackers can look at contract transactions to determine values stored in the state of the contract.
Hence, any private data should either be stored off-chain, or carefully encrypted. No-sensitive data is stored in current contract.
Copy link