Executive Summary

Report Objectives

This document details the source code review of Lightlink Bridge Validator and Keeper. The purpose of the assessment was to perform the whitebox testing of the Bridge’s validator and Keeper before going into production and identify potential threats and vulnerabilities.

Scope of Work

TypeDetails
Target System NameLightLink ValidatorLightLink Keeper
Source Code URL(s)/ https://github.com/pellartech/ll-bridge-validator/commit/34977df32ecd8079701181cb632d4dbb2b119aac
https://github.com/pellartech/ll-bridge-keeper/commit/17a7d47e8b8473d8e34b44681f8a0d35bdb6aa47
Type of TestWhite-Box Testing
LanguageTypeScript

Project Objectives

Conduct a thorough code-review and vulnerability research on Lightlink Bridge’s Validator and keeper code.

Results

The codebase is written in good quality. No significant issue was identified which can affect the functionality of the bridge. Error handling should be improved. For overall understanding of the codebase, proper commenting should be applied.

Summary of Findings

Our Team Found

# of issuesSeverity Of the Risk
0Critical Risk Issues(s)
1High Risk Issues(s)
2Medium Risk Issues(s)
1Low Risk Issues(s)
0Informatory Risk Issues(s)
Lightlink Bridge - pie chart

Methodology

Planning

Our general approach during this code audit was as follows:

Code review

The first step of our audit was a thorough code review. We read and go through the code, ensuring a comprehensive understanding of the bridge's architecture not limited to the superficial aspects of the code.

Static analysis 

The next stage in our audit involved conducting a rigorous static analysis using SonarQube. This powerful tool allowed us to analyze the codebase in a non-runtime environment. With SonarQube, we were able to detect & verify bugs, vulnerabilities, and code smells to mitigate any risks and improve overall code quality.

Dynamic Analysis

Our final audit stage consisted of dynamic analysis. This process involved integrating test functions within the code and running the program with a range of inputs. Our goal was to trigger and observe any suspicious or unexpected behaviors that might emerge during runtime. This approach helped us to identify potential issues that only manifest themselves under specific conditions or when the system is operational.

Risk Classification

This report is adhering to the classification standards defined as per OWASP. The summarized version is presented below.

LightLink Bridge Architecture

 System Architecture

The LightLink Bridge system architecture is based on a Proof-of-Authority mechanism with an external validator set that stakes its individual reputation in order to earn incentives for the trades on the LightLink bridge. This ensures a robust and interconnected framework enabling seamless asset transfers between Ethereum's Layer 1 and Layer 2. The bridge architecture consists of several key components, each playing a crucial role in the overall operation of the bridge. The bridge architecture consists of three main components, the front-end dApp, validator nodes and a single keeper node.

Architecture Diagram

Note:

While the provided documentation for the LightLink architecture is commendable and provides an essential overview, it could benefit from further detailed elaboration. A comprehensive breakdown of each component's functionality, interactions, and the specific roles they play in the broader architecture could enhance the depth and clarity of the document. Additionally, providing more detailed technical insights, workflows, and possible edge cases could facilitate a more robust understanding of the system. This will not only aid in improved transparency but also contribute to more effective and inclusive future audits, and security assessments.

Workflow of Keeper and Bridge

Findings

Potential Risks of Unauthorized Access on Redis

SeverityLikelihoodAffected Files
HighMedium  src/config/environments/production.ts

Description:

Redis is used by both Validator and Keeper. Under current implementation of the validator and keeper code, there was no authentication applied for redis connection. If the same code is deployed on production without using proper authentication mechanism, it can lead to potential RCE on the redis server. Open redis servers are prone to Remote Code Execution issues.

export default class RedisProvider {
@inject(LLogger.name) private _logger: LLogger

protected redis: RedisClientType
public readonly REDIS_PORT = CONFIG.REDIS.PORT
public readonly REDIS_HOST = CONFIG.REDIS.HOST
public readonly REDIS_PREFIX = CONFIG.REDIS.PREFIX

constructor(@inject(LLogger.name) logger: LLogger) {
this._logger = logger
this.redis = createClient({
url: `redis://${CONFIG.REDIS.HOST}:${CONFIG.REDIS.PORT}`
})

this.redis.on('error', err => {
this.disconnect()
this._logger.info(`Redis Client Error: ${err}`)
})
this.connect().then(() => {
this._logger.info('Redis Client Connected')
})
}

/ll-bridge-keeper-17a7d47e8b8473d8e34b44681f8a0d35bdb6aa47/src/providers/redis.ts

Proof Of Concept (POC)

File: “ll-bridge-keeper-17a7d47e8b8473d8e34b44681f8a0d35bdb6aa47/src/config/environments/production.ts”

REDIS: {
HOST: String(process.env.REDIS_HOST || '127.0.0.1'),
PORT: Number(process.env.REDIS_PORT || 6379),
PREFIX: String(process.env.REDIS_PREFIX || '__ll_bridge_keeper__')
},

Mitigation:

The production code must incorporate the use of a Redis instance with the appropriate credentials in order to address potential unauthorized access to Redis.

Developer Response:

All validators are private and run via a docker package prepared by the development team. There is no way to access the packages in an unauthorized manner and all validators may only be run via a whitelisting process via the distributed docker solution.

Redis also does not store any security data.

Auditor Response:

The response from developers is acceptable and acknowledged. Open Redis instance are prone to RCE issues. We reported the issue in that sense. It will be a good practice to use credentials on redis too.

Potential DOS Risk due to whitelisted validators

SeverityLikelihoodAffected Files
MediumLowsrc/modules/v1/chain/chain.indexer.ts

Description:

A temporary DOS can happen if following scenarios are carried out

  1. User submits a transaction for bridging
  2. Validator submits a pending proof in DB
  3. Validators are updated on chain by admin
  4. Keeper will submit transaction on chain but as validators are updated hence validation will not be successful
  5. Keeper will repeatedly send request until it is successful
  6. If there are 10 such proofs in DB then it can cause temporary DOS as in the produceValidatedProofs() method , getVerifiedProofs() is taking a limit of 10 , and it will fetch 10 such proofs which can cause issues.
public async produceValidatedProofs() {
const requests = await Promise.all([
this._v1BridgeRepository.getVerifiedProofs({
chain: ChainSupported.Lightlink,
consensusPowerThreshold: CONFIG.MODULES.V1.BRIDGE.CONTRACTS.LIGHTLINK.CONSENSUS_POWER_THRESHOLD,
limit: 10
})
// this._v1BridgeRepository.getVerifiedProofs({
// chain: ChainSupported.Ethereum,
// consensusPowerThreshold: CONFIG.MODULES.V1.BRIDGE.CONTRACTS.ETHEREUM.CONSENSUS_POWER_THRESHOLD,
// limit: 10 // })
])
// const items = [...requests[0], ...requests[1]]
const items = [...requests[0]]

for (const item of items) {
await this.submitProofToChain(item)
}}

src/modules/v1/chain/chain.service.ts

Mitigation:

It is important to include a mechanism that ensures when onchain validators are updated, the corresponding whitelisted validators should also be updated to prevent any potential scenarios.

Developer Response:

Configuration operating with validators is as follows:

1 x Validator with 40 voting power run by Pellar

2 x Validator with 20 voting power run by Pellar

Voting power required for authorisation of bridge transfers = 80

At the moment in the unlikely case that a validator is removed from the pool then we can monitor any proofs that are invalid due to this circumstance and process the withdrawal.

To mitigate the chance of us not being able to reach 80 voting power we are onboarding partners who will be running validators each with 20 power of their own.

Auditor Response:
The response from developers is acceptable and acknowledged.

Potential Risk of Price Manipulation Due to single source

SeverityLikelihoodAffected Files
MediumHighsrc/config/environments/production.ts 

Description:

Keeper is fetching prices from a single oracle source i.e gate.io , which is defined in src/config/environments/production.ts

EXTERNAL_PARTIES: {
GATE_IO: {
API_ROOT_V4: 'https://api.gateio.ws/api/v4'
}}}

In case of a price manipulation on gate.io , bridge can be affected.

Mitigation:

It is advised to define multiple price oracles and aggregate the price or use Chainlink’s price oracle as it aggregate price from multiple sources.

Developer Response:
We are not currently fetching prices from any oracle so this is a non-issue. If/when we do then we will make sure to either use an aggregated source such as Chainlink or aggregate sources on our own.

Auditor Response:

The response from developers is acceptable and acknowledged.

Information Disclosure due to Improper Error Handling

SeverityLikelihoodAffected Files
LowHighsrc/core/apiError.ts

Description:

When a Keeper node is deployed it deploys certain API endpoints to which the validator can see the proofhashes & signatures. If the data is not sent in proper format then the endpoint returns the full stack error. This stack shows the full path of the deployed keeper, which is a bad practice & leaks information.

Technical Description:

In ​​’src/core/apiError.ts’ if the type is ‘RequestException’ then it is returning exceptionInstance.resolve(). resolve() method is also returning the “this.stack”.

export default class ErrorFactory {
static handle(type: Keys, error: IException, context?: IExceptionContext): IException {
const exceptionInstance = new exceptionMaps[type](error, context)
switch (type) {
case 'BizException':
case 'RequestException':
return exceptionInstance.resolve()
}
}
public resolve() {
return {
status: this.status,
code: this.code,
message: this.message,
details: this.details,
context: this.context,
stack: this.stack
}}}

Proof Of Concept (POC)

  1. Make a bad parameterized request at the /api/v1/bridges/finalized/proofs
  2. Analyze the response

Mitigation:

It’s advised not to return the stack and detailed messages, rather it is recommended to only return generic error codes upon a bad request. This obscurity prevents attackers from gaining insights into the backend infrastructure from the error information, thereby limiting their scope for black box exploitation. It mitigates the potential reconnaissance performed by malicious actors.

Developer Response:
Access to the stack has been removed in production mode.

Auditor Response:
The response from developers is acceptable and acknowledged.

SonarQube Test Reports

We utilized SonarQube, a powerful tool designed to inspect and analyze the entire codebase for Keeper and Validator. The primary purpose of SonarQube is to conduct static code analysis, systematically examining the software without executing it, to identify potential vulnerabilities, bugs, and code smells. Code smells refer to certain characteristics or patterns in the code that could potentially indicate deeper problems. SonarQube provided us with several insightful suggestions for enhancing our code's quality and security. The following points highlight these suggested improvements and identified issues:

SonarQube Issues Report for Keeper

RuleSeverityFileLineDescription
typescript:S4144MAJORkeeper:src/core/apiError.ts24Update this function so that its implementation is not identical to the one on line 15.
typescript:S4323MINORkeeper:src/helpers/utils.ts4Replace this union type with a type alias.
typescript:S4323MINORkeeper:src/modules/v1/bridge/bridge.repository.ts38Replace this union type with a type alias.
typescript:S1135INFOkeeper:src/modules/v1/chain/chain.service.ts537Complete the task associated to this "TODO" comment.
typescript:S4822MAJORkeeper:src/modules/v1/system/system.indexer.ts
25Consider using 'await' for the promise inside this 'try' or replace it with 'Promise.prototype.catch(...)' usage.
typescript:S4822MAJORkeeper:src/modules/v1/system/system.indexer.ts37Consider using 'await' for the promise inside this 'try' or replace it with 'Promise.prototype.catch(...)' usage.
typescript:S4325MINORkeeper:src/providers/external.ts71This assertion is unnecessary since it does not change the type of the expression.

SonarQube Issues Report For Validator

typescript:S1135INFOvalidator:src/modules/v1/bridge/bridge.service.ts213Complete the task associated to this "TODO" comment.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts35Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S4323MINORvalidator:src/modules/v1/chain/chain.service.ts55Replace this union type with a type alias.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts206Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts379Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts556Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts730Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts884Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts1038Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S3776CRITICALvalidator:src/modules/v1/chain/chain.service.ts1197Refactor this function to reduce its Cognitive Complexity from 18 to the 15 allowed.
typescript:S4822MAJORvalidator:src/modules/v1/system/system.indexer.ts18Consider using 'await' for the promise inside this 'try' or replace it with 'Promise.prototype.catch(...)' usage.

NPM Audit Report

Npm offers a dependency auditing option which should be run by the developer to ensure usage of secure dependencies and to mitigate the risk of supply chain attacks.

Vulnerable
Dependency
TitleSeverityViaEffectsRange
word-wrapword-wrap vulnerable to Regular Expression Denial of Servicemoderateword-wrap-<1.2.4
semversemver vulnerable to Regular Expression Denial of Servicemoderatesimple-update-notifier-<=5.7.1
nodemon-moderatesimple-update-notifier-2.0.19 - 2.0.22
tough-cookietough-cookie Prototype Pollution vulnerabilitymoderatetough-cookie-<4.1.3
minimistPrototype Pollution in minimistcritical-optimist<=0.2.3
mongooseMongoose Prototype Pollution vulnerabilitycritical--6.0.0 - 6.11.2
optimistPrototype Pollution in minimistcriticalminimistswig-templates>=0.6.0
swig-templatesArbitrary local file read vulnerability during template renderingcriticalswig-templates, optimistswig-email-templates*
class-validatorSQL Injection and Cross-site Scripting in class-validatorcritical--<0.14.0
@aws-sdk/client-cognito-identity-high@aws-sdk/client-sts@aws-sdk/credential-provider-cognito-identity3.12.0 - 3.54.1
@aws-sdk/client-sts-highfast-xml-parser@aws-sdk/client-cognito-identity, @aws-sdk/credential-providers<=3.54.1
cheerio-highcss-selectswig-email-templates0.19.0 - 1.0.0-rc.3
css-selectInefficient Regular Expression Complexity in nth-checkhighnth-checkcheerio<=3.1.0
fast-xml-parserfast-xml-parser vulnerable to Regex Injection via Doctype Entities, fast-xml-parser vulnerable to Prototype Pollution through tag or attribute namehighfast-xml-parser@aws-sdk/client-sts<=4.2.3
json5Prototype Pollution in JSON5 via Parse Methodhigh--<1.0.2
luxonLuxon Inefficient Regular Expression Complexity vulnerabilityhigh--1.0.0 - 1.28.0
@aws-sdk/credential-provider-cognito-identity-high@aws-sdk/client-cognito-identity-3.12.0 - 3.347.0
@aws-sdk/credential-providers-high@aws-sdk/client-cognito-identity, @aws-sdk/client-sts, @aws-sdk/credential-provider-cognito-identity-<=3.347.0
swig-email-templates-highcheerio, swig-templates->=2.0.0

Mitigation

It is advised to run ‘npm audit’ before putting it into production and using ‘npm audit fix’ to mitigate the possible issues

Disclaimer: Source Code Review

The source code review is conducted for the purpose of providing constructive feedback and suggestions to enhance the quality and security of the software. This review is not meant to criticize or undermine the efforts of the developers or individuals involved in the project. The reviewer shall not be liable for any loss, damages, or issues resulting from the use or implementation of the feedback provided. The scope of this review is limited to the specific code presented, and any subsequent changes to the code may not be reflected in this evaluation. It is essential to understand that the reviewer cannot guarantee the identification of all potential issues or vulnerabilities.

The developers are responsible for maintaining the software's security and quality. All information discovered during the review shall be treated as confidential. The use of third-party components is assumed to be compliant. The reviewer's assessment is independent, and there is no affiliation with any organization related to the software under review. The developers are encouraged to perform multiple audits, periodically reevaluate and update the code to address potential issues. By proceeding with the review results, all parties agree to accept the terms of this disclaimer.

Introduction

BonqDAO, a decentralized, and over-collateralized lending platform built on the Polygon network, suffered a major security breach on February 2, 2023. The platform enables users to borrow against their own tokens by locking them up in a Trove, a smart contract controlled only by the users, and minting a low volatility payment coin, BEUR, pegged to the Euro.

The attack exploited a vulnerability in the integration of the Tellor Oracle system, which is used by BonqDAO to obtain token price information. By manipulating the price of AllianceBlock's WALBT tokens, the attacker managed to steal 100 million BEUR stablecoins and 120 million Wrapped AllianceBlock Tokens (WALBT). This article will provide an in-depth analysis of the hack, detailing the impact it had on both the platform and its users.

Hack Impact

The hack had a profound impact on BonqDAO, the affected tokens, and the DeFi ecosystem at large. The following are the key areas affected by the attack:

Direct Losses

The attacker was able to steal 100 million BEUR stablecoins and 120 million WALBT tokens. The stolen funds were converted into other cryptocurrencies and laundered through Tornado Cash, a privacy tool for Ethereum transactions, making it harder to trace and recover the stolen assets.

Token Price Drop

The news of the hack led to a significant decline in the value of the affected tokens. Bonq Euro (BEUR), a stablecoin pegged to the Euro, fell to an all-time low of $0.15 on February 3, which is a severe depreciation for any stablecoin. Additionally, the AllianceBlock Token (ALBT) experienced a major hit as collateral damage from the attack.

Understanding Tellor Oracle

Tellor is a decentralized Oracle protocol that provides an immutable, open, and permissionless network for data reporting and validation. It enables anyone to provide data and allows everyone to verify its accuracy. In the context of BonqDAO, Tellor is used as a price oracle to obtain token price information.

To become a reporter for the Tellor Oracle, a user needs to stake a certain amount of TRB tokens. Once the required stake is deposited, the user is eligible to report data using the submitValue function. However, it is important to note that if a user reports invalid or malicious data, their staked amount may be slashed as a penalty. This mechanism encourages accurate reporting and helps maintain the integrity of the data provided on the network.

The submitValue function, as seen in the code snippet below, is part of the TellorFlex contract:

BonqDAO - submitValue Function

This function allows the reporter to submit a value to the Tellor Oracle network. In summary, the Tellor Oracle allows anyone to become a reporter by staking a certain amount of TRB tokens and submitting data using the submitValue function. This function ensures that only users with a sufficient stake can report data, and the slashing mechanism discourages the submission of invalid data, maintaining the integrity and accuracy of the data provided on the network

Breaking Down the Attack: Exploiting BonqDAO 

Step 1: Staking on TellorFlex Oracle

The attacker began by staking 10 TRB (Tellor's native tokens) on the TellorFlex oracle allowing them to become a reporter.This is a necessary step for anyone who wants to submit a new data point to the Tellor Oracle. The staking serves as collateral, meaning if the submitted data is found to be incorrect or malicious, the stake can be forfeited.

Step 2: Manipulating the price of WALBT tokens

The attacker submitted a false price for WALBT tokens, setting it at 5,000,000 USD. This is much higher than the actual price. The Tellor Oracle, which is used by BonqDAO to get price information, accepted this manipulated price.

Step 3: Creating a Trove and borrowing BEUR tokens

With the manipulated price in place, the attacker created a Trove within the BonqDAO platform. A Trove is a smart contract that allows users to lock up their tokens as collateral and borrow other tokens against it. The attacker deposited a small amount of WALBT tokens (0.1 WALBT) into the Trove, and due to the inflated price, they were able to borrow $100 million worth of BEUR tokens, which are stablecoins pegged to the Euro.

Step 4: Converting BEUR tokens to other cryptocurrencies

The attacker then used Uniswap, a decentralized exchange, to swap the borrowed BEUR tokens for other cryptocurrencies like USDC, a stablecoin pegged to the US Dollar.

Step 5: Creating a second Trove and manipulating the price again

The attacker created a second Trove with WALBT tokens and deposited 13.2 WALBT tokens into it. Then, the attacker staked another 10 TRB on the TellorFlex Oracle, but this time submitted a much lower price for WALBT tokens (0.0000001 USD).

Step 6: Liquidating Troves at a low token price

With the low WALBT token price in place, the attacker proceeded to liquidate multiple Troves of WALBT tokens. Liquidation in this context means repaying the borrowed BEUR tokens and getting the locked WALBT tokens back. Since the WALBT price was now extremely low, the attacker could repay the borrowed BEUR tokens with a minimal amount of WALBT tokens, effectively stealing a large amount of WALBT tokens in the process.

Step 7: Moving the stolen funds

After successfully stealing 113.8 million WALBT tokens and 98 million BEUR tokens, the attacker began moving the funds out through Tornado Cash, a privacy tool for Ethereum transactions. This allowed them to launder the stolen funds and make it harder to trace.

The attacker exploited the vulnerability in the integration of the Tellor Oracle system, which allowed them to manipulate the price and ultimately steal a large amount of tokens.

Mitigation Strategies and the Dangers of Instant Price Usage

To prevent similar vulnerabilities from being exploited in the future, several mitigation strategies can be implemented to secure the price oracle integration within DeFi platforms like BonqDAO.

Use Time-Weighted Average Price (TWAP)

Instead of relying on instant prices, DeFi platforms should use the Time-Weighted Average Price (TWAP) over a predetermined period. This approach minimizes the risk of price manipulation by averaging out the price fluctuations and provides a more accurate representation of the token's value.

Multiple Price Oracle Sources

Integrating multiple price oracle sources can help minimize the impact of a single oracle failure or manipulation. By aggregating data from different sources, the platform can cross-verify the accuracy of the price data and ensure a more reliable feed.

Monitor for Suspicious Price Changes

Implementing real-time monitoring systems to detect sudden and significant price changes can help in identifying potential price manipulation attempts. By setting up alerts for abnormal price fluctuations, the platform can take corrective actions and prevent further exploitation.

In the case of BonqDAO, the reliance on instant prices from the Tellor oracle made the platform susceptible to price manipulation. By using the latest price data without allowing time for validation and scrutiny by other network participants, BonqDAO exposed itself to the risk of accepting false price information. This vulnerability enabled the attacker to manipulate the price of WALBT tokens and exploit the platform, causing significant financial losses. Implementing the above-mentioned mitigation strategies can help platforms like BonqDAO to better secure their price oracle integrations and prevent future attacks.

Transaction Analysis

Attacker's Address: 0xcAcf2D28B2A5309e099f0C6e8C60Ec3dDf656642

Attacker's Contract: 0xed596991ac5f1aa1858da66c67f7cfa76e54b5f1

TellorFlex Contract: 0x8f55D884CAD66B79e1a131f6bCB0e66f4fD84d5B

BONqDAO Contract: 0x4248fd3e2c055a02117eb13de4276170003ca295

Attack Transaction 1: 0x31957ecc43774d19f54d9968e95c69c882468b46860f921668f2c55fadd51b19
Attack Transaction 2: 0xa02d0c3d16d6ee0e0b6a42c3cc91997c2b40c87d777136dedebe8ee0f47f32b1

After the successful execution of both transactions, the attacker was able to steal 113.8 million WALBT tokens and 98 million BEUR tokens. They then proceeded to convert the BEUR tokens into stablecoins like USDC using Uniswap, totaling around $534K.The attacker moved the funds to the Ethereum blockchain and started to launder their gains through Tornado Cash, a privacy solution. This allowed them to obscure the origin of the funds, making it more difficult for anyone to trace their movements.

Conclusion

The BonqDAO hack serves as a stark reminder of the risks involved in decentralized finance and the need for stringent security measures to protect users' funds. By exploiting a vulnerability in the price oracle integration, the attacker was able to manipulate the price of the WALBT token and cause significant financial losses for the BonqDAO platform and its users.

This incident highlights the crucial role that thorough, smart contract audits play in safeguarding DeFi platforms. By conducting a comprehensive audit of the smart contracts involved, potential vulnerabilities and weaknesses can be identified and addressed before an attacker can exploit them. For a reliable and professional smart contract audit, consider partnering with BlockApex.io, a leading provider of security solutions for blockchain-based projects. Through rigorous examination and assessment, BlockApex.io can help ensure that your smart contracts are secure, protecting your platform and users from potential hacks and exploits.

Introduction

Orion Protocol is a liquidity aggregator that connects to major cryptocurrency exchanges and swap pools, both centralized and decentralized. This allows users to obtain the best price for their trades from a single platform. On February 2, 2023, the Orion Protocol was exploited on both Ethereum and BNB Chain, leading to a loss of approximately $3 million in assets due to a reentrancy vulnerability in one of its core contracts.

Hack Impact

The attackers exploited a reentrancy vulnerability in the Orion Protocol's core contract, ExchangeWithOrionPool, by constructing a fake token (ATK) with self-destruct capability that led to the transfer() function. They then carried out the attack by depositing 0.5 USDC and using a flash loan of 191,606 USDC to swap through the Orion pool via the path (USDC-ATK-USDT).

Understanding Reentrancy Attack

A reentrancy attack is a type of security vulnerability in a smart contract, where a malicious actor exploits the contract's logic to repeatedly call a function before the previous function call finishes. This can lead to unintended consequences, such as stealing funds or locking them indefinitely.

Function Explanation: doSwapThroughOrionPool

Orion protocol - doSwapThroughOrionPool function

The purpose of the doSwapThroughOrionPool function is to perform a token swap using the Orion Pool, a decentralized exchange (DEX) that allows users to trade tokens based on the provided swap data.

This function is called by external contracts or users to initiate a token swap. The doSwapThroughOrionPool function takes the following parameters:

user: The address of the user initiating the swap.

to: The recipient address where the swapped tokens will be sent.

swapData: A struct containing the swap data, including path, amount to spend, amount to receive, whether it's an exact spend or exact receive, and the supporting fee.

Vulnerability: Insufficient Validation of Path Addresses

The primary issue in the doSwapThroughOrionPool function is the insufficient validation of the path addresses provided in the swapData parameter. The function allows a malicious user to provide a path that includes an arbitrary contract address, which can potentially execute unintended actions.

The code snippet below shows where the new path is created, and the addresses in the provided path are copied to the new path:

The function does not validate whether the addresses in the path are legitimate tokens, pools, or factories. An attacker can exploit this vulnerability by including a malicious contract address in the path that manipulates the token transfers or performs other unexpected actions when called within the swap process.

Breaking Down the Attack

Preparation

The attacker creates a malicious token contract (ATK) with a transfer() function that has a callback mechanism. They transfer and authorize the ATK token in preparation for the attack.

Flash loan

The attacker takes a flash loan of 191,606 USDC from a lending platform.

Swap tokens

The attacker calls the doSwapThroughOrionPool() function of the vulnerable smart contract to swap the borrowed 191,606 USDC using the exchange path [USDC, ATK, USDT]. The ATK token is the malicious token created by the attacker, which will be used for the callback.

Trigger callback

As the doSwapThroughOrionPool() function in Orion protocol executes the token swap, it triggers the transfer() function of the malicious ATK token. The transfer() function of the ATK token then calls back the depositAsset() function of the vulnerable smart contract through the callback mechanism.

Exploit reentrancy

Due to the lack of reentrancy protection in the depositAsset() function of Orion protocol, the attacker is able to re-enter the depositAsset() function through the ATK token's transfer() function during the token swap. As a result, the smart contract records the attacker's deposit amount as the full flash loan amount of 191,606 USDT, inflating the balance of tokens in the contract.

Price increase

The inflation of the attacker's deposit amount in the Orion protocol's smart contract, combined with the token swap, creates an artificial price increase for the USDT tokens. The smart contract calculates the difference in USDT token balance before and after the swap, resulting in the attacker receiving more USDT tokens than they should have.

Withdraw profits

After accumulating a significant deposit amount, the attacker calls the withdrawal function of the vulnerable smart contract to withdraw 5,689,532 USDT, which includes their inflated deposit and the profit from the exploit.

Pay back the flash loan

The attacker pays back the original flash loan amount of 2,853,326 USDT, keeping the remaining profit for themselves.

Convert and transfer profits

The attacker swaps their remaining profit of 2,836,206 USDT for 1,651 WETH and transfers it to their wallet or another platform for further anonymity.the attacker transferred approximately 1100 ETH into Tornado Cash, a privacy-focused protocol, to obfuscate their tracks.
The attacker replicated this attack on the BNB Chain, generating an additional profit of $191,434. Consequently, the total profit amassed from both attacks reached approximately $3 million, with $191,434 earned on the BNB Chain and $2,836,206 on the Ethereum network.

Mitigation Strategies for Reentrancy Attacks

Use reentrancy guards

Implement reentrancy guards in your smart contracts, such as the nonReentrant modifier provided by OpenZeppelin. This will prevent functions from being called multiple times before the original call has completed.

Implement a checks-effects-interactions pattern

Ensure that your smart contract functions follow the checks-effects-interactions pattern, where you first perform checks, then update the contract state, and finally interact with external contracts. This can prevent unexpected state changes during external contract interactions.

Transactions Involved

ATK Token(Fake): 0x64acd987a8603eeaf1ee8e87addd512908599aec

Attacker’s 1st Address: 0x3dabf5e36df28f6064a7c5638d0c4e01539e35f1

Attacker’s 2nd Address: 0x837962b686fd5a407fb4e5f92e8be86a230484bd

Attacker’s Contract(ETH): 0x5061F7e6dfc1a867D945d0ec39Ea2A33f772380A

Vulnerable Contract: 0x420a50a62b17c18b36c64478784536ba980feac8

Attack Txn (ETH): 0xa6f63fcb6bec8818864d96a5b1bb19e8bd85ee37b2cc9

BNB Chain Details

ATK Token(Fake): 0xc4da120a4acf413f9af623a2b9e0a9878b6a0afe

Attacker’s 1st Address: 0x3dabf5e36df28f6064a7c5638d0c4e01539e35f1

Attacker’s 2nd Address: 0x837962b686fd5a407fb4e5f92e8be86a230484bd

Attacker’s Contract(BSC): 0x84452042cB7be650BE4eB641025ac3C8A0079b67

Attack Txn (BSC): 0xfb153c572e304093023b4f9694ef39135b6ed5b251545317

Conclusion

Reentrancy attacks are a significant threat to smart contracts, potentially causing substantial financial losses and damage to the reputation of projects. By understanding the nature of these attacks and implementing the recommended mitigation strategies, developers can significantly reduce the risk of such vulnerabilities in their smart contracts.

It is crucial to follow best practices in smart contract development and stay updated on the latest security techniques. Regular audits, testing, and code reviews are essential to identify potential vulnerabilities and ensure the safety and integrity of your smart contracts.

Finally, to ensure the highest level of security for your smart contracts, consider partnering with professional auditing firms like BlockApex.io. Their expertise in smart contract audits and security assessments can provide invaluable insights and help safeguard your projects from potential threats and vulnerabilities.

Introduction

Zunami is a decentralized protocol operating in the Web3 space, specializing in issuing aggregated stablecoins like UZD and zETH. These stablecoins are generated from omnipools that employ various profit-generating strategies. Recently, the protocol was exploited, resulting in a loss of $2.1M. The exploit specifically targeted Zunami's UZD and zETH liquidity pools on the Curve ecosystem. This analysis delves into the impact and mechanisms for this kind of vulnerability.

Hack Impact

The Zunami Protocol experienced a severe price manipulation attack that led to a loss of approximately $2.1M. The attacker was able to exploit Zunami’s zETH and UZD liquidity pools on the Curve platform. This caused the zStables (zETH and UZD) to depeg dramatically - zETH by 85% and UZD by 99%.

The Hack Explained:

Zunami

Transactions Involved

 Source

Protocol Response

Conclusion

The Zunami Protocol hack serves as a cautionary tale about the risks and vulnerabilities present in complex decentralized financial systems. The exploitation capitalized on multiple weaknesses in Zunami's design, leading to a substantial loss of funds and trust. Given the growing number of such exploits, it's imperative for projects in the DeFi space to take robust security measures seriously, undergoing rigorous audits from a reputed audit firm like Blockapex and implementing strong protective mechanisms to shield both their assets and their user base.

Introduction

Jimbo's Protocol is a decentralized finance (DeFi) system built on the Arbitrum chain. The protocol uses a semi-stable floor price for its ERC-20 token, $JIMBO, backed by a treasury of Ether (ETH). However, despite its pioneering efforts to maintain on-chain liquidity and price floors, Jimbo's Protocol recently faced a Flash loan attack. On May 28, 2023, a sophisticated attacker managed to exploit a loophole in the protocol's slippage control mechanism, walking away with approximately $7.5 million in ETH.

Hack Impact

The attack had immediate and severe consequences for Jimbo's Protocol and its community. The attacker successfully drained a large portion of the treasury's ETH, affecting the protocol's stability and trustworthiness. The incident led to a sharp 40% decline in the value of $JIMBO, eroding the token's market position and investor confidence.

What is Liquidity Rebalancing?

In decentralized finance (DeFi), liquidity rebalancing is the process of reallocating assets within a liquidity pool to ensure efficient capital utilization and smooth price discovery. In simple terms, it means making sure there's enough "money" in the right places in a trading pool to make trades easy and fair.

What is Slippage?

Slippage occurs when the price of an asset changes between the time you place an order and the time the order is fulfilled. In DeFi, it's particularly important to control slippage to prevent significant price fluctuations that could result from large trades.

How Does Rebalancing Work in $JIMBO?

In $JIMBO's case, rebalancing happens via three primary functions: Shift(), Reset(), and Recycle(). These are triggered based on the state of different bins—Floor Bin, Active Bin, and Trigger Bin.

Understanding the Shift() Function in $JIMBO Protocol

The Shift() function plays a crucial role in the $JIMBO protocol, automatically activating when the Active Bin (the pool bin where the current trading price resides) moves past the Trigger Bin. The function performs two primary actions:

Simultaneously, the Shift() function also invokes a Reset() function call to redistribute the remaining $JIMBO tokens within the pool.

For more comprehensive insights into how these bins operate within the protocol, please refer to this Link

A Detailed Look at the Attack Mechanics

Step 1: Obtain Initial Funds

Step 2: Inflate $JIMBO Price

Step 3: Transfer Tokens to Contract

Step 4: Invoke Shift()

Step 5: Crash the Market Price

Step 6: Trigger Another Rebalance

Step 7: Exploit and Profit

The attacker then returns the initial ETH borrowed through the flash loan, retaining a substantial net gain of 7.5 million.

Vulnerability Analysis

The critical flaw was the absence of slippage controls in the Shift() function, enabling the attacker to trigger rebalancing actions at artificial price levels. Once liquidity was redeployed at these inflated prices, the attacker could then manipulate the market to purchase tokens at a much lower cost, making a substantial profit in the process.

Transaction involved

Attacker's Addresses:

Attacker’s Address(ETH)

Notable Transactions:

Attacker Contract:

JimboController Contract
Attack Transaction

JIMBO's response to the hack

Response

The protocol also sent an On chain message to the hacker

How to Prevent Such Exploits

A system with a more sophisticated rebalancing mechanism that includes proper slippage control, could have likely prevented this exploit. By undergoing a comprehensive security audit, potentially from firms like BlockApex, protocols can identify and address these vulnerabilities before they're exploited.

Conclusion

The Jimbo Protocol incident serves as a cautionary tale that even innovative DeFi protocols are vulnerable to sophisticated attacks. This event highlights the importance of comprehensive security audits, a service that BlockApex specializes in, to identify and mitigate vulnerabilities in DeFi protocols. As the DeFi sector continues to evolve, so should its security measures to protect investor interests and maintain system integrity.

Introduction

Platypus Finance is a decentralized finance (DeFi) platform. On February 17, 2023, the platform was hacked, resulting in a loss of approximately $8.5 million worth of assets. In this hack analysis, we will delve into the details of the attack, the vulnerability that was exploited, and the impact it had on the platform and its users.

Hack Impact

The financial implications of this exploit are substantial, with the attacker managing to siphon off a significant amount of funds. By exploiting the vulnerability in the emergencyWithdraw function of the MasterPlatypusV4 contract in Platypus Finance, the attacker was able to create "bad debt" in the system, allowing them to acquire the debt's upside.

As a result the attack  hack had a significant impact on the Platypus Finance ecosystem and its users. The attack resulted in a loss of approximately $8.5 million worth of tokens and caused a large decline in the price of the USP stablecoin. The price of USP fell by more than 66% compared to its intended $1 peg, and the project's native PTP token lost a quarter of its value in a day. The attacker was able to mint 40 million USP tokens from the Platypus Finance's contract MasterPlatypusV4 contract using 44 million Platypus LP-USDC tokens as collateral. However, the team was able to recover approximately $2.4 million USDC from the attack contract, reducing the overall impact of the hack.

Vulnerable Functions Overview

The emergencyWithdraw Function (Picture credit)

Platypus Finance - EmergencyWithdraw Function

The emergencyWithdraw function allows users to withdraw their funds from the pool without accounting for rewards. This function is intended for emergency situations and is designed to be used when a user needs to exit the pool quickly without waiting for the rewards to accrue.
The emergencyWithdraw function takes a pool ID as an argument and retrieves the corresponding pool and user information from the mapping. It then resets the rewarder to zero, transfers the user's LP tokens to their address, updates the sumOfFactors, and sets the user's amount, factor, and rewardDebt to zero

The isSolvent function (Picture Credit)

Platypus Finance - _isSolvent function

The isSolvent function in Platypus Finance contract is used to determine if a user's collateral position is solvent, meaning if the value of the collateral is greater than or equal to the outstanding debt. It takes the user's address, the token address, and a boolean indicating if the position is being opened or closed as arguments.

The isSolvent function first retrieves the user's debtShare and checks if it is zero. If it is, it returns true, indicating that the position is solvent. If the debtShare is non-zero, it calculates the debtAmount by multiplying the debtShare with the totalDebtAmount and interest since the last accrual, and dividing the result by the totalDebtShare. It then checks if the debtAmount is less than or equal to the borrow limit if the position is being opened or the liquidate limit if the position is being closed.

Security Flaw in the Functions

The vulnerability in the emergencyWithdraw() and _isSolvent() functions is due to an improper solvency check that allows an attacker to withdraw their collateral without fully paying back their debt. Specifically, the solvency check only considers whether the user's debt amount exceeds the borrowing limit, but does not take into account the actual debt owed by the user.

This means that an attacker can deposit collateral to borrow USP tokens, but then withdraw their collateral without paying back the full amount of USP borrowed. Since the solvency check only considers the borrowing limit, the attacker can appear solvent and pass the check even if they owe a significant amount of debt.

The vulnerability is caused by a logical flaw in the code, as the solvency check should also consider the actual debt owed by the user, not just the borrowing limit. The code should be updated to accurately calculate the amount of debt owed and ensure that it is fully paid back before allowing a user to withdraw their collateral.

The Devious Hack: A Step-by-Step Explanation

Taking a Flash Loan

The attacker borrowed a massive 44 million USDC from a lending protocol, just like taking out a huge loan from a bank.

Depositing USDC

The attacker deposited the 44 million USDC into a platform called Platypus USDC Asset (LP-USDC) and received 44 million LP-USDC tokens similar to a person depositing money in a bank and receiving a line of credit.

Borrowing More Money (USP Tokens) with Collateral

Using the 44 million LP-USDC tokens as collateral, the attacker borrowed 41.79 million USP tokens from the system, similar to taking out a second loan by leveraging the line of credit received earlier.

Exploiting a Loophole in the System

The attacker found a vulnerability in the system's solvency check (emergencyWithdraw and isSolvent functions) , which didn't properly account for the debt amount. This loophole allowed the attacker to withdraw the initial collateral without repaying the borrowed USP tokens, as the debt was within the 95% borrowing limit cap.

Withdrawing the Initial Collateral

The attacker used the loophole to withdraw their initial collateral (44 million LP-USDC tokens), equivalent to taking back their initial deposit from the bank without repaying the second loan.

Cashing Out the Collateral

The attacker withdrew the 44 million USDC from the LP-USDC Asset, converting the collateral back into cash.

Profiting by Swapping USP Tokens for Other Assets

The attacker swapped the 41.79 million USP tokens for various stablecoins across multiple platforms, making a total profit of approximately $8.5 million, similar to exchanging the second loan for valuable assets.

Repaying the Flash Loan

Finally, the attacker repaid the initial 44 million USDC flash loan, keeping the $8.5 million profit.

Recommendations for Enhanced Security

To mitigate the vulnerability in the emergencyWithdraw() and _isSolvent() functions in Platypus Finance, the solvency check should be updated to consider the actual debt owed by the user rather than only taking into account the debt limit. This can be achieved by implementing a check that validates the user's current debt amount against their collateral value.

This will ensure that users cannot withdraw their collateral without fully paying back their debt, thereby preventing the creation of "bad debt" in the system. It is also recommended to perform thorough testing and auditing of the updated solvency check to ensure its effectiveness in preventing such attacks. Additionally, implementing a time-delayed withdrawal feature can provide an additional layer of security and prevent attackers from instantly withdrawing their collateral in case of a vulnerability exploit.

Transaction Analysis

Attacker's address: 0xeff003d64046a6f521ba31f39405cb720e953958

Attack transaction: 0x1266a937...

Attack contract: 0x67afdd6489d40a01dae65f709367e1b1d18a5322

The hack resulted in USP being depegged by over 50%, and the stolen $8.5 million remains in the hacker's contract. $1.5 million of stolen USDT has been blacklisted.

References:


Conclusion: Lessons Learned and the Importance of Security

In conclusion, the MasterPlatypusV4 hack, a.k.a Platypus Finance hack, highlights the importance of robust security measures and thorough audits in the world of decentralized finance. The attacker exploited a vulnerability in the emergencyWithdraw() function, which only checked the isSolvent variable and disregarded the debt amount. By carefully navigating through the various steps, the attacker managed to make a profit of approximately $8.5 million in stablecoins, causing significant damage to the protocol and its users.

This incident serves as a reminder that even well-designed protocols can have unforeseen vulnerabilities. It is crucial for DeFi projects to implement stringent security practices, regularly update their code, and undergo comprehensive audits by reputable firms. To ensure the highest level of security and protection for your DeFi project, consider partnering with a trusted auditing firm like BlockApex for thorough and reliable smart contract audits. By taking these precautions, projects can minimize the risk of such hacks and foster a safer ecosystem for all participants.

Introduction

Dexible Finance is a decentralized finance (DeFi)  aggregator. It allows users to trade tokens seamlessly across multiple DEXes in a single interface, optimizing for the best rates and lowest fees. However, on February 17, 2023, a security vulnerability was exploited in the platform's recently introduced v2 smart contracts, resulting in the loss of approximately $2 million worth of tokens. In this hack analysis, we will discuss the nature of the vulnerability, the attackers' exploitation method, and the overall impact of the hack on the platform and its users.

Hack Impact

The Dexible Finance hack had significant consequences for the platform and its users. The attackers managed to exploit a vulnerability in the selfSwap function of the platform's v2 smart contracts, which allowed them to siphon tokens from users who had granted permission to Dexible to manage their tokens. The hack affected a total of 17 user accounts, with the majority of losses coming from a single address belonging to BlockTower Capital, a prominent investment firm.

In total, approximately $1.5 million was stolen on the Ethereum network and an additional $450k on other platforms (Arbitrum and BSC). The stolen funds were sent to Tornado Cash, a privacy-focused mixer service, to obfuscate the attackers' tracks and make it difficult to trace the origin of the stolen funds.

Vulnerable Functions Overview

The selfSwap function

Dexible - Self swap function

The selfSwap function is a part of Dexible's smart contract and is responsible for enabling users to perform token swaps without the involvement of an affiliate or automatic discounts. The function takes a SwapTypes.SelfSwap request as an input, which contains information about the tokens to be swapped, the fees, and the routing details.

It creates a SwapTypes.SwapRequest object with the provided data and initializes a SwapMeta object that stores various metadata about the swap, including fee details, gas amounts, and balances.Once the SwapMeta object is created, the selfSwap function calls the fill function, passing the SwapRequest and SwapMeta objects as arguments. After the fill function returns the updated SwapMeta object, the selfSwap function proceeds with the postFill method to finalize the token swap.

The fill function

The fill function is called by the selfSwap function and is responsible for executing the token swap based on the routing information provided in the SwapRequest object. It first performs a preCheck to ensure that the request and metadata are valid. Then, it iterates through the specified routes and approves each route amount for the respective spender. The function then calls the router specified in each route using the routerData provided.

If the router call is unsuccessful, the function reverts with an error message. Otherwise, the fill function calculates the output amount of the token swap and checks if it is sufficient compared to the requested amount. If the output is insufficient, the function reverts with an error message. Finally, the fill function returns the updated SwapMeta object to the selfSwap function.

Security Flaw in the Functions

The vulnerability in the selfSwap and fill functions lies in the lack of validation and verification for the router address (routerID) provided in the SwapRequest object. This omission allows an attacker to pass their own contract address as the router, which is not verified on-chain. As a result, the fill function performs a delegate call to the attacker's provided routerData, allowing the attacker to transfer the victim's approved tokens directly to their account.
The absence of routerID validation and an on-chain verification mechanism in these two functions leaves the smart contract susceptible to manipulation by malicious actors, leading to the loss of tokens for the affected users.

Attacker's Playbook: Exploiting the Vulnerability

Step 1: Crafting the malicious contract

The attacker creates their own malicious contract that mimics a legitimate DEX. This contract contains a "transferFrom" function, which can transfer tokens from an approved account to the attacker's address.

Step 2: Preparing the selfSwap request

The attacker prepares a SwapTypes.SelfSwap request containing the details of the tokens they want to swap. They provide the address of their malicious contract as the router in the routing information, bypassing any on-chain verification.

Analogy: It's like a thief providing false bank details to a victim, tricking them into sending money to the thief's account instead of a legitimate bank.

Step 3: Calling the selfSwap function

The attacker calls the selfSwap function on the Dexible contract, passing their crafted selfSwap request. The Dexible contract processes the request and calls the fill function with the SwapRequest and SwapMeta objects.

Step 4: Executing the fill function

During the execution of the fill function, the malicious router address provided by the attacker is used to perform a delegate call to the routerData. Since there is no validation for the routerID, the fill function ends up calling the attacker's malicious contract instead of a legitimate DEX.

Step 5: Profiting from the vulnerability

The malicious contract's "transferFrom" function is executed, transferring the victim's approved tokens directly to the attacker's address. The attacker then converts these tokens to other cryptocurrencies or sends them to privacy-focused services like Tornado Cash to obfuscate their tracks.

Analogy: The thief withdraws the funds from the victim's account and moves them through various accounts or services to hide the source of the stolen funds.
By following these steps, the attacker can exploit the vulnerability in the selfSwap and fill functions to steal tokens from unsuspecting users who have granted the Dexible contract permission to manage their tokens.

Recommendations for Enhanced Security

Verify the router address on-chain

Implement a mechanism within the selfSwap function to ensure that the provided router address is a legitimate DEX. An on-chain allowlist or a registry of approved DEX addresses can be used to validate the router address before proceeding with the swap.

Analogy: A bank verifying the authenticity of a recipient bank account before allowing a funds transfer.

Whitelist legitimate routers

Include a whitelist of approved router addresses in the Dexible contract. By doing so, you can prevent users from interacting with malicious contracts, as only approved router addresses will be allowed to process swaps.

Analogy: A bank only allowing transfers to verified and approved external accounts.

Enforce routerID validation in the fill function

Add validation checks within the fill function to ensure that the routerID provided in the selfSwap request matches a known and approved router address. This added layer of security can help prevent unauthorized access to users' funds.

Analogy: A bank double-checking the recipient account details during a transfer process to prevent fraudulent transactions.

Regularly review and update approval allowances:

Users should regularly review and revoke token allowances granted to contracts. By doing so, they can minimize the risk of losing funds due to vulnerabilities in the contracts they interact with.

Analogy: A bank customer frequently reviewing and updating their list of approved payees to ensure that their funds are only accessible to trusted parties.
By implementing these recommendations, the security of the Dexible contract can be significantly improved, reducing the risk of similar exploits in the future and providing a safer environment for users to manage their digital assets.

Transaction Analysis: Tracing the Attacker's Steps

Transaction: 0x138daa4c

To understand the attacker's actions, we can analyze the transactions on the blockchain associated with the hack. We'll use the  transaction 0x138daa4c as a reference.

Step 1: Identify the attacker's address

The attacker's address can be found from the  transaction: 0x684083f312ac50f538cc4b634d85a2feafaab77a

Dexible Tokens management history Image

Step 2: Analyze the transaction details

By inspecting the transaction details on a blockchain explorer like Etherscan, we can find information such as:

The selfSwap request data, The gas used and the status of the transaction (successful or failed). The tokens involved in the swap and the amounts transferred.

Step 3: Follow the flow of funds

After the attacker successfully exploited the vulnerability, they converted the stolen tokens to other cryptocurrencies or sent them to privacy-focused services like Tornado Cash to obscure their tracks. By tracing the flow of funds from the attacker's address, we can gather information about their actions after the hack.

How they transferred stolen tokens to other cryptocurrencies

Step 4: Observe related transactions

Examining other transactions associated with the attacker's address can provide insights into any patterns or similarities with other hacks. Additionally, it can help identify if the attacker has targeted multiple platforms or contracts.

By analyzing the transactions related to the hack, we can better understand the attacker's methods, actions, and the impact of the exploit. This information can be useful for preventing similar incidents in the future and improving the security of smart contracts.

Conclusion: Lessons Learned and the Importance of Security

The Dexible hack serves as a stark reminder of the importance of thorough security measures and audits in the world of decentralized finance. By exploiting a vulnerability in the selfSwap and fill functions, the attacker was able to bypass important safeguards and steal millions of dollars from unsuspecting users.

The incident highlights the need for rigorous security practices such as verifying router addresses on-chain, implementing whitelists, and regularly reviewing approval allowances. Following these recommendations can help prevent similar attacks and create a safer ecosystem for users to interact with DeFi platforms.

One crucial step in ensuring the security of smart contracts is to have them audited by reputable firms like BlockApex. A thorough audit by experienced professionals can help identify potential vulnerabilities and weaknesses in the code, allowing developers to address them before deployment. This proactive approach to security can save projects from significant financial losses and protect the trust of their user base.

In conclusion, the Dexible hack underscores the importance of robust security measures in the rapidly evolving DeFi landscape. By learning from these incidents and implementing best practices, we can build a more secure and trustworthy decentralized finance ecosystem.

Introduction

BlockApex (Auditor) was contracted by ZeroLiquid (Client) to conduct a Smart Contract Audit/ Code Review. This document presents the findings of our analysis, which started on 11th July ‘2023.

Name
ZeroLiquid Protocol
Audited by
Kaif Ahmed | Muhammad Jarir Uddin
Platform
Ethereum and EVM Compatible Chains | Solidity
Type of review
Manual Code Review | Automated Tools Analysis
Methods
Architecture Review | Functional Testing | Computer-Aided Verification | Manual Review
Git repository/ Commit Hash
Private Repo | 041d229b92c97e96d21f5023d43b5dd55803f047
White paper/ Documentation
Protocol Overview
Document log
Initial Audit Completed: Aug 3rd ‘2023
Final Audit Completed: Aug 10th ‘2023

Scope

The shared git-repository was checked for common code violations and vulnerability-specific probing to detect major issues/vulnerabilities. Some specific checks are as follows:

Code reviewCode review Functional review
ReentrancyUnchecked external callBusiness Logics Review
Ownership Takeover ERC20 API violationFunctionality Checks
Timestamp DependenceUnchecked mathAccess Control & Authorization
Gas Limit and LoopsUnsafe type inferenceEscrow manipulation
DoS with (Unexpected)
Throw
Implicit visibility levelToken Supply manipulation
DoS with Block Gas LimitDeployment ConsistencyAsset’s integrity
Transaction-Ordering
Dependence
Repository ConsistencyUser Balances manipulation
Style guide violationData ConsistencyKill-Switch Mechanism
Costly LoopOperation Trails & Event
Generation

Project Overview

ZeroLiquid is a decentralized protocol that offers non-liquidation, interest-free, self-repaying loans. The protocol allows users to utilize Liquid Staking Derivative (LSD) assets to issue loans against their collateral. Upon depositing LSD tokens, users receive a synthetic token, zETH, which can be traded to provide immediate liquidity. The unique feature of ZeroLiquid is that loans are self-repaying, using the staking rewards generated by the collateral. Users have the option to early unstake by repaying the remaining loan amount.

ZeroLiquid employs a multi-pronged approach to safeguard the zETH/ETH peg:

System Architecture

The ZeroLiquid protocol is built on a robust and secure architecture that enables seamless asset management and loan issuance. The protocol periodically harvests staking rewards, credited proportionally to users, reducing their outstanding debt. This unique feature allows the loans to be self-repaying, providing a hassle-free experience for the users.

The architecture comprises several key components, including the ZeroLiquid, ZeroLiquidToken, and Steamer smart contracts.

ZeroLiquid

The ZeroLiquid contract is the core of the protocol, managing the deposit of LSD tokens, the issuance of loans, and the harvesting of staking rewards. It ensures the accurate tracking of assets and the proportional distribution of rewards to reduce the outstanding debt of users.

ZeroLiquidToken

The ZeroLiquidToken contract creates and manages zETH, the protocol's synthetic ETH. It includes functionality for minting and burning tokens and managing the protocol's flash loan feature. The ZeroLiquidToken contract interacts with a number of other contracts and libraries to provide its functionality.

Steamer

The Steamer contract is a crucial component of the ZeroLiquid Protocol, responsible for managing the protocol's native token. It includes functionality for minting and burning tokens and managing the protocol's flash loan feature.

These components work together to provide a robust and secure platform for creating and managing over-collateralized stablecoins. The architecture is designed to be modular and extensible, allowing for future upgrades and improvements. This ensures that the protocol can continue to evolve and adapt to the changing needs of the DeFi ecosystem.

In addition to these features, ZeroLiquid also allows users to unstake their collateral early by repaying the remaining loan amount. Users can do this by depositing either the LSD token they used as collateral or zETH. Furthermore, users can liquidate a portion or all of their collateral anytime, providing maximum flexibility and control over their assets.

Methodology & Scope

The codebase was audited using a filtered audit technique. A pair of two (2) security researchers scanned the codebase in an iterative process for eighteen (18) days.

Starting with the recon phase, a basic understanding was developed, and the security researchers worked on developing presumptions for the developed codebase and the relevant documentation/whitepaper. Furthermore, the audit moved on with the manual code reviews to find logical flaws in the codebase complemented with code optimizations, software, and security design patterns, code styles, best practices, and identifying false positives detected by automated analysis tools.

Audit Report

Executive Summary

Our team performed a technique called Filtered Audit, where two individuals separately audited the Zero Liquid Protocol.

After a thorough and rigorous manual testing process involving line-by-line code review for bugs, an automated tool-based review was carried out using Slither for static analysis.

All the flags raised were manually reviewed and re-tested to identify the false positives

Our Team Found

# of issuesSeverity Of the Risk
0Critical Risk Issues(s)
0High Risk Issues(s)
2Medium Risk Issues(s)
5Low Risk Issues(s)
8Informatory Risk Issues(s)

Key Findings

#FindingsRiskStatus
1.Unchecked Delegate Call and Return ValuesMediumResolved
2.Missing notPaused Modifier in Several FunctionsMMediumResolved
3.Insufficient Input Validation in setPendingAdmin,
setSentinel, and setKeeper Functions
LowResolved
4.Protocol Fee and flashMintFee Can Be Set to 99%LowResolved
5.Insufficient Input Validations in initialize FunctionsLowResolved
6.Potential Underflow in conversionFactor CalculationLowResolved
7.flashLoan Function Does Not Check If amount Is ZeroLowResolved
8.Insufficient Testing of the Zero Liquid ProtocolInfoAcknowledged
9.Inefficient Mutexes and Insufficient EventsInfoAcknowledged
10.Insufficient Event Emission During Whitelist UpdateInfoAcknowledged
11.Order of Layout ViolatedInfoAcknowledged
12.Structs Should Be Moved to Interface for Increased
Modularity
InfoAcknowledged
13.Typo in Variable NameInfoAcknowledged
14.Inefficient Initialization of isPaused VariableInfoAcknowledged
15.Missing Input Validation in setTransferAdapterAddressInfoAcknowledged

Detailed Overview

Medium-risk issues

1. Unchecked Delegate Call and Return Values

File: ZeroLiquid.sol

Status: Resolved

Function Name: -

Description: 

The sweepRewardTokens function uses delegateCall to call the claim function on the contract at the msg.sender address. However, the return value of the delegatecall is not checked, which could lead to unexpected behavior. The scenario arises due to two potential reasons as mentioned above;

Code Affected:

msg.sender.delegatecall(
abi.encodeWithSignature("claim(address)", yieldToken)
)

Impact:

If the claim function fails due to some reasons which are unclear since the codebase is not shared, the sweepRewardTokens function will continue execution, potentially leading to incorrect state changes in the ZeroLiquid protocol smart contracts complex. This could lead to loss of funds or other unexpected behavior. Additionally, claim() can be used to reenter the smart contract in potential cases like the novel cross-functional reentrancy.

Recommendation:

It is recommended to confirm from the development team the following;

  1. Ensure the contract address stored as a keeper role is always safe.
  2. Check the return value of the delegatecall and revert if it is false. This will ensure that the sweepRewardTokens function fails if the claim function fails.

Developer's Response:

This function is no longer needed and will be removed.

Auditor's Response:

Acknowledged with no follow-up required.

Additional Comments:

The linked reports emphasize on severity of this issue as high (or medium, at minimum) in similar contexts. Fortifying the claim that the set of developer assumptions can be broken where either the smart contract in silo or the complete smart contract complex is led to an undetermined state - The remediations in all issues linked here focus on two aspects; 1) to document the intended functionality for successful target contract call even if no code executed AND 2) check for return values in any case. E.g. links 1 & 2 focus on unidentified state changes within the smart contracts, 3 & 4 focus on risks associated with such flow of execution, 5 & 6 emphasize on defining the severity of issue for impact and relevant likelihood - Link 1, Link 2, Link 3, Link 4, Link 5, Link 6

2. Missing notPaused Modifier in Several Functions

File: Streamer.sol

Status: Resolved

Description:

Several functions in the Steamer contract that should only be callable when the contract is not paused are missing the notPaused modifier. This includes the deposit, withdraw, claim, and exchange functions. These functions all modify the smart contract's state and should not be callable when the contract is paused. The contract will be entirely vulnerable in such cases, rendering the emergency mechanisms ineffectual.

Code Affected:

function deposit(
    uint256 amount,
    address owner
) external override nonReentrant {
    ...
}

function withdraw(
    uint256 amount,
    address recipient
) external override nonReentrant {
    ...
}
function claim(
    uint256 amount,
    address recipient
) external override nonReentrant {
    ...
}

function exchange(
    uint256 amount
) external override nonReentrant onlyBuffer {
    ...
}

Impact:

If these functions are called while the contract is paused, they could lead to unexpected behavior or potential vulnerabilities. For example, if the deposit function is called while the contract is paused, this could lead to an imbalance between the total amount of tokens deposited and the total amount of tokens that can be withdrawn.

Recommendation:

Add the notPaused modifier to all functions that should only be callable when the contract is not paused. This will ensure that these functions behave as expected and prevent potential vulnerabilities.

Developer Response:

deposit, withdraw & claim functions do not need to check pause state, only exchange function does. Even if user deposits debt token while paused the unconverted debt tokens can still be withdrawn. Anyother specific function you can name which should check the pause state?

Auditor’s Response:

Acknowledged. No other external functions perform critical updates to the smart contract's global state and hence there is no followup required in this regard.

Low-risk Issues

3. Insufficient Input Validation in setPendingAdmin, setSentinel, and setKeeper Functions

File: ZeroLiquid.sol

Status: Resolved

Description:

The setPendingAdmin, setSentinel, and setKeeper functions do not perform any checks on their parameters. This could allow the admin to set the pending admin, sentinel, or keeper to the zero address.

Code Affected:

function setPendingAdmin(address value) external override {
    _onlyAdmin();
    pendingAdmin = value;
    emit PendingAdminUpdated(value);
}
function setSentinel(address sentinel, bool flag) external override {
    _onlyAdmin();
    sentinels[sentinel] = flag;
    emit SentinelSet(sentinel, flag);
}
function setKeeper(address keeper, bool flag) external override {
    _onlyAdmin();
    keepers[keeper] = flag;
    emit KeeperSet(keeper, flag);
}

Impact:

If the setPendingAdmin, setSentinel, or setKeeper functions are called with the zero address, this could cause the contract to be in an incorrect state. This could lead to unexpected behavior and potential loss of control over the contract.

Recommendation:

Add validations to the setPendingAdmin, setSentinel, and setKeeper functions to ensure that the address parameters are not the zero address.

Developer Response:

Setters only enable or disable any specific address using boolean flag, doesn't need any unnecessary validation or concerete meaning of "insufficient" should be provided otherwise. Are you talking about checking if the address was previously enabled or disabled before repeating the same action again?

Auditor’s Response:

Acknowledged with no followup required.

4. Protocol Fee and flashMintFee Can Be Set to 99%

File: ZeroLiquid.sol

Status: Resolved

Description:

The setProtocolFee and the  setFlashFee functions allow the admin to set the protocol fee to any value less than BPS (10,000 basis points, representing 100%). This means the admin could set the fee to 99.99%, which would be extremely high and could deter users from using the contract’s functionality.

Code Affected:

function setProtocolFee(uint256 value) external override {
    _onlyAdmin();
    _checkArgument(value <= BPS);
    protocolFee = value;
    emit ProtocolFeeUpdated(value);
}
function setFlashFee(uint256 newFee) external onlyAdmin {
    if (newFee >= BPS) {
        revert IllegalArgument();
    }
    flashMintFee = newFee;
    emit SetFlashMintFee(flashMintFee);
}

Impact:

If the protocol fee or the flash loan minting fee is set to a very high value, this could deter users from using the contract. This could reduce the utility and adoption of the contract.

Recommendation:

Consider implementing a reasonable upper limit (and a lower limit for the flashMintFee state variable) for the protocol fee. This could be a fixed value or a value that governance can update

Developer Response:

These variables are purposefully dynamic so that they can be adjusted if needed, but the percentage is transparent on chain and not something hidden from users

Auditor’s Response:

Acknowledged with no follow-up required.

Additional Comments:

This is a vague intention by design since a common user of DeFi believes in the specifications and figures drawn over the publicly accessible interfaces like a protocol's website or the technical documentation. A security first design ensures that in no case a functionality be abused by an adversary, It is therefore suggested that the developer team specify a range and enforce it in the smart contract code so that even in case of compromise the adversary can never enforce an illegal fee for users.

Final update:

Fixed for both functions.

5. Insufficient Input Validations in Initialize Functions

File: Steamer.sol, ZeroLiquid.sol

Status: Resolved

Description:

The initialize function in the Steamer and the ZeroLiquid smart contracts lack sufficient input validation. This function is responsible for setting up the initial state of the contracts, including setting up roles and initializing various state variables. However, it performs no checks on the input parameters such as _syntheticToken, _underlyingToken, and _buffer in the Steamer contract and in the ZeroLiquid contract this missing validation could allow the admin to set invalid values for the protocolFee parameter.

Code Affected:

//Steamer.sol 
function initialize(
    address _syntheticToken,
    address _underlyingToken,
    address _buffer
) external initializer {
   // no validations in place
    ...
}

//ZeroLiquid.sol 
function initialize(
 InitializationParams memory params
) external initializer {
    _checkArgument(params.protocolFee <= BPS); //no ranges enforced
    // no validations in place
    debtToken = params.debtToken;
    admin = params.admin;
    steamer = params.steamer;
    minimumCollateralization = params.minimumCollateralization;
    protocolFee = params.protocolFee;
    protocolFeeReceiver = params.protocolFeeReceiver;
    …
}

Impact:

Without proper input validation, the initialize function could be called with invalid or malicious parameters, leading to unexpected behavior or potential vulnerabilities. For example, if the _syntheticToken or _underlyingToken parameters are set to the zero address, this could lead to loss of funds or other unexpected behavior. Similarly, if the _buffer parameter is set to an address that does not implement the expected interface, this could lead to reverts or other unexpected behavior.

Recommendation:

Add appropriate input validations to the initialize function in both smart contracts. Ensure that the provided addresses are not zero and that the tokens have the expected properties (e.g., decimals). This will prevent the function from being called with invalid or malicious parameters.Similarly, for the ZeroLiquid contract, add checks to the initialize function to ensure that all parameters are valid. For example, you could check that the admin and steamer addresses are not zero and that the protocolFee is within a reasonable range.

Developer Response:

Doesn't need any unnecessary validations or concrete meaning of "insufficient" should be provided otherwise.

Auditor’s Response:

Acknowledged with no followup required.

Additional Comments:

A faulty initialization in both cases will lead to redeployment of the smart contract complex incurring additional gas cost and addresses recalculation.

6. Potential Underflow in conversionFactor Calculation

File: Streamer.sol

Status: Resolved

Description:

The conversionFactor is calculated as 10 ** (debtTokenDecimals - underlyingTokenDecimals). If underlyingTokenDecimals is greater than debtTokenDecimals, this will result in a revert due to underflow.

Code Affected:

conversionFactor = 10 ** (debtTokenDecimals - underlyingTokenDecimals);

Impact:

An underflow in the conversionFactor calculation during the initialization could lead to the failure of contract deployment. Since the initialize function is a crucial part of contract setup, an error during this stage would prevent the contract from being set up correctly. This would render the contract unusable, as the conversionFactor is a critical parameter used in various functions within the contract.

Recommendation:

Add a check in the initialize function to ensure that debtTokenDecimals is greater than or equal to underlyingTokenDecimals before calculating conversionFactor. This will prevent an underflow during the initialization and ensure that the contract is set up correctly. If the check fails, the function should revert with an appropriate error message, indicating that the initialization parameters are invalid.

Developer Response:

What is the potential range of numbers in which underflow occurs?

Auditor’s Response:

For all cases; where, debtTokenDecimals < underlyingTokenDecimals the call to initialize will fail leading to failure in executing the deployment script. Since there exists no check to ensure the reason of failure, debugging will add to overall complexity.

Developer Response:

In our case, debtTokenDecimals (zETH) is 18 & underlyingTokenDecimals (WETH) is also 18 therefore the condition debtTokenDecimals < underlyingTokenDecimals will never be true, but still an edge case was identified therefore, we agree this is a low severity issue.

Auditor’s Response:

Acknowledged with no follow-up required.

7. flashLoan Function Does Not Check If amount Is Zero

File: ZeroLiquidToken.sol

Status: Resolved

Description:

The flashLoan function does not check if the amount parameter is zero. If the amount is zero, this would result in a no-operation and waste gas.

Code Affected:

function flashLoan(
    IERC3156FlashBorrower receiver,
    address token,
    uint256 amount,
    bytes calldata data
)
    external
    override
    nonReentrant
    returns (bool)
{
    if (token != address(this)) {
        revert IllegalArgument();
    }

    if (amount > maxFlashLoan(token)) {
        revert IllegalArgument();
    }

    uint256 fee = flashFee(token, amount);

    _mint(address(receiver), amount);

    if (receiver.onFlashLoan(msg.sender, token, amount, fee, data) != CALLBACK_SUCCESS) {
        revert IllegalState();
    }

    _burn(address(receiver), amount + fee); // Will throw error if not enough to burn

    return true;
}

Impact:

If the flashLoan function is called with the amount parameter set to zero, this would result in a no-op and waste gas.

Recommendation:

Add a check to the flashLoan function to ensure that the amount parameter is not zero.

Developer Response:

Transaction will revert if amount is zero.

Auditor’s Response:

Acknowledged with no followup required.

Additional Comments:

Since the ERC20 standard allows a zero (0) mint, burn and transfers, the Zero Liquid Token contract does not have any additional constraints in place to support the claim that transaction will revert if amount is zero.

Informational Issues

8. Insufficient Testing of the Zero Liquid Protocol

File: **

Status: Acknowledged

Description:

Throughout the codebase, the testing is insufficient to support the positive and negative test cases from the Zero Liquid protocol engineering team. The substandard testing of such a complex smart contract suite hints at a critical flaw in the Zero Liquid protocol as many cases remain unexplored.

Impact:

The lack of comprehensive testing for the ZeroLiquid protocol can have several potential impacts:

  1. Undiscovered Bugs and Vulnerabilities: Without thorough testing, there may be bugs or security vulnerabilities in the code that remain undiscovered. These could potentially be exploited by malicious actors, leading to financial loss for users or even the failure of the protocol.
  2. Unpredictable Behavior: Insufficient testing can lead to unpredictable behavior in edge cases or under unusual market conditions. This could result in unexpected losses for users or other undesirable outcomes.
  3. Maintenance and Upgrade Challenges: Without comprehensive tests, it can be more difficult to maintain the protocol or to make upgrades in the future. This could slow down the development process and make it harder to respond to changes in the market or the wider DeFi ecosystem.

Recommendation:

It is highly recommended to have a sufficient suite of test cases executed over the production-ready smart contract code to achieve the satisfaction of all functionality working as expected. Additionally, the testing can ensure edge cases are handled elegantly by the protocol ensuring a higher level of security.

Additional Comments:

The Zero Liquid Protocol contains a complex codebase and architecture with a lesser modularity associated as per the industry standard. Hence, sufficient testing refers to the fact that an exhaustive list of test cases be necessarily executed over the production ready code to, at the very least, have the happy test cases work as expected. Not having a test suite creates a lesser level of trust for security researchers as third party reviewers of the protocol's code.

9. Inefficient Mutexes and Insufficient Events

File: ZeroLiquid.sol

Status: Acknowledged

Description:

The setTransferAdapterAddress function in the ZeroLiquid contract uses a lock modifier commonly employed to prevent reentrancy attacks. However, given this function's behavior, the reentrancy risk seems minimal. Overusing the lock modifier can increase gas costs for the function calls. Additionally, the function does not emit an event after changing the state variable transferAdapter, a recommended practice for transparency and traceability.

Code Affected:

function setTransferAdapterAddress(
        address transferAdapterAddress
    ) external override lock {
        _onlyAdmin();
        transferAdapter = transferAdapterAddress;
    }

Recommendation:

Fixed-Code

event TransferAdapterChanged(address newAddress);

function setTransferAdapterAddress(
    address transferAdapterAddress
) external override { 
    _onlyAdmin();
    transferAdapter = transferAdapterAddress; 
    emit TransferAdapterChanged(transferAdapterAddress);
}

10. Insufficient Event Emission During Whitelist Update

File: ZeroLiquidToken.sol

Status: Acknowledged

Description:

The setWhitelist function updates the whitelist of addresses that are allowed to mint new tokens, but it does not emit an event when the whitelist is updated. This makes it harder to track changes to the whitelist.

Code Affected:

function setWhitelist(address minter, bool state) external onlyAdmin {
    whitelisted[minter] = state;
}

Impact:

Without an event being emitted when the whitelist is updated, it is harder to track changes to the whitelist. This could make it more difficult for users to understand who is allowed to mint new tokens, and it could make it harder to audit the contract.

Recommendation:

Consider emitting an event in the setWhitelist function when the whitelist is updated. This could include the address that was added or removed, and the new state of the whitelist.

11. Order of Layout Violated

File: ZeroLiquid.sol

Status: Acknowledged

Description:

The contract's layout order is violated from the Solidity docs. External functions that implement some functionality should be placed on top so that bytecode saves the code for interactive functions as soon as possible, incurring lesser gas costs to storage when it tries to fetch the code of the function.

Code Affected:

Various parts of the contract.

Impact:

The layout order in the contract does not affect its functionality, but it could increase the gas cost for storage and execution.

Recommendation:

Consider reordering the functions in the contract according to the Solidity docs. Place external functions that implement some functionality on top.

12. Structs Should Be Moved to Interface for Increased Modularity

File: Streamer.sol

Status: Acknowledged

Description:

Several structs are defined in the Steamer contract that could be moved to the ISteamer interface. This would increase the modularity, reusability, and readability of the code.

Code Affected:

struct Account {...}
struct UpdateAccountParams {...}
struct ExchangeCache {...}

Impact:

By defining these structs in the Steamer contract rather than the ISteamer interface, the code is less modular and reusable. Other contracts that interact with the Steamer contract might need to define their versions of these structs, leading to code duplication and potential inconsistencies.

Recommendation:

Move the definition of these structs to the ISteamer interface. This will make the code more modular and reusable and ensure that other contracts that interact with the Steamer contract can use the same definitions of these structs.

13. Typo in Variable Name

File: Streamer.sol

Status: Acknowledged

Description:

There is a typo in the variable name normaizedAmount in the exchange function. This could lead to confusion for developers reading or maintaining the code.

Code Affected:

uint256 normaizedAmount = _normalizeUnderlyingTokensToDebt(amount);

Recommendation:

Correct the typo in the variable name normalized Amount to normalizedAmount. This will improve the readability of the code.

14. Inefficient Initialization of isPaused Variable

File: Streamer.sol

Status: Acknowledged

Description:

The isPaused variable is initialized to false in the initialize function, which is unnecessary as boolean variables in Solidity are false by default.

Code Affected:

isPaused = false;

Impact:

This unnecessary initialization of isPaused to false results in additional gas costs during contract deployment. While the impact is low, it is a waste of gas and could be easily avoided.

Recommendation:

Remove the unnecessary initialization of isPaused to false in the initialize function. This will reduce the gas cost of contract deployment.

15. Missing Input Validation in setTransferAdapterAddress Function

File: ZeroLiquid.sol

Status: Acknowledged

Description:

The setTransferAdapterAddress function does not perform any checks on its parameter. This could allow the admin to set the transfer adapter address to zero.

Code Affected:

function setTransferAdapterAddress(
    address transferAdapterAddress
) external override lock {
    _onlyAdmin();
    transferAdapter = transferAdapterAddress;
}

Impact:

If the setTransferAdapterAddress function is called with the zero address, this could cause the contract to be in an incorrect state. This could lead to unexpected behavior and potential loss of control over the contract. Additionally, it will incur gas costs to reset the contract with the correct values to call the relevant functions again.

Recommendation:

Add a check to the setTransferAdapterAddress function to ensure that the address parameter is not the zero address.

Disclaimer

The smart contracts provided by the client for audit purposes have been thoroughly analyzed in compliance with the global best practices to date w.r.t cybersecurity vulnerabilities and issues in smart contract code, the details of which are enclosed in this report.

This report is not an endorsement or indictment of the project or team, and they do not in any way guarantee the security of the particular object in context. This report is not considered and should not be interpreted as an influence on the potential economics of the token, its sale, or any other aspect of the project.

Crypto assets/tokens are the results of emerging blockchain technology in the domain of decentralized finance, and they carry with them high levels of technical risk and uncertainty. No report provides any warranty or representation to any third-Party in any respect, including regarding the bug-free nature of code, the business model or proprietors of any such business model, and the legal compliance of any such business. No third party should rely on the reports in any way, including for the purpose of making any decisions to buy or sell any token, product, service, or another asset. Specifically, for the avoidance of doubt, this report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and is not a guarantee as to the absolute security of the project.

Smart contracts are deployed and executed on a blockchain. The platform, its programming language, and other software related to the smart contract can have vulnerabilities that can lead to hacks. The scope of our review is limited to a review of the Solidity code and only the Solidity code we note as being within the scope of our review within this report. The Solidity language itself remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer or any other areas beyond Solidity that could present security risks.

This audit cannot be considered a sufficient assessment regarding the utility and safety of the code, bug-free status, or any other statements of the contract. While we have done our best in conducting the analysis and producing this report, it is important to note that you should not rely on this report only - we recommend proceeding with several independent audits and a public bug bounty program to ensure the security of smart contracts.

Introduction

BlockApex (Auditor) was contracted by ScriptTV (Client) for the purpose of conducting a Smart Contract Audit/ Code Review. This document presents the findings of our analysis which started on 30th Jan ‘2023.

Name
Script TV
Auditor 
BlockApex
Platform
Ethereum | Solidity
Type of review
Manual Code Review | Automated Tools Analysis
Methods
Architecture Review | Functional Testing | Computer-Aided Verification | Manual Review
Git repository/ Commit Hash
6cf8da8bb236cbd9cbd2834a9d280e2164fd577f
White paper/ Documentation
https://whitepaper.script.tv
Document log:
Initial Audit Completed: Feb 6, 2023
Final Audit Completed: Mar 6, 2023

Scope

The git-repository shared was checked for common code violations along with vulnerability-specific probing to detect major issues/vulnerabilities. Some specific checks are as follows:

Code reviewFunctional review
Reentrancy Unchecked external callBusiness Logics Review
Ownership TakeoverERC20 API violationFunctionality Checks
Timestamp DependenceUnchecked mathAccess Control & Authorization
Gas Limit and LoopsUnsafe type inferenceEscrow manipulation
DoS with (Unexpected) ThrowImplicit visibility levelToken Supply manipulation
DoS with Block Gas LimitDeployment ConsistencyAsset’s integrity
Transaction-Ordering DependenceRepository ConsistencyUser Balances manipulation
Style guide violationData ConsistencyKill-Switch Mechanism
Costly LoopOperation Trails & Event Generation

Project Overview

Script.TV is a decentralized video delivery network that furnishes an expansive range of blockchain-enabled solutions to the problems related to the traditional video-streaming sector. The platform offers high-quality video streaming as well as multiple incentive mechanisms for decentralized bandwidth and content-sharing at a reduced cost as compared to conventional service providers. Script.TV offers an online TV platform in which both users and content publishers earn valuable tokens through video streaming. A user-first watch-2-earn solution that allows users to earn rewards on- and off-chain by upgrading their ScriptGlasses NFT.

System Architecture

The workflow of the protocol is that of a Watch to Earn. A user can mint three types of glasses (Common, Rare & SuperScript) by burning $SPAY where each type has two base attributes i.e. Durability and Gem tier. The durability of the glass increases as it levels up (via watching content) and the gem tier increases as gems are integrated with the glass rewarding users with $SPAY.

To onboard users, the protocol will follow a freemium model where 100,000 common glasses will be minted for free. These common glasses will have a fixed allowable watch time and will yield a fixed payout i.e., equivalent to a base common glass with no gems. The free mints phase will operate simultaneously with paid mints. To differentiate the free mint from the paid ones, there are certain milestones that are pegged to watch time enabling a user to modify their glasses and enhance their reward/ unit energy. Users can watch content on the platform for as long as they want, however, there are limits to the daily watch time to earn SPAY tokens.

Recharge vouchers add to the gamification element in the protocol and also serve as an incentivization mechanism for the users to upgrade their levels in order to avail of a discount on their recharge cost. To mint the recharge voucher, the user will have to burn their SPAY tokens. This is an optional burnable event as the user can choose not to purchase the recharge voucher after the eligibility criteria are met.

Methodology & Scope

The codebase was audited using a filtered audit technique. A band of three (3) auditors scanned the codebase in an iterative process for a time spanning one (1) week. Starting with the recon phase, a basic understanding was developed and the auditors worked on developing presumptions for the shared codebase and the relevant documentation/ whitepaper. Furthermore, the audit moved on with the manual code reviews with the motive to find logical flaws in the codebase complemented with code optimizations, software, and security design patterns, code styles, best practices, and identifying false positives that were detected by automated analysis tools.

AUDIT REPORT

Executive Summary

Our team performed a technique called “Filtered Audit,” where the contract was separately audited by four individuals. After a thorough and rigorous process of manual testing, an automated review was carried out using Slither & Mythril for static analysis and Foundry for fuzzing invariants. All the flags raised were manually reviewed and re-tested to identify the false positives.

meter

Our team found

# of issues Severity of the risk
1Critical Risk issue(s)
1High Risk issue(s)
2Medium Risk issue(s)
3Low Risk issue(s)
5Informatory issue(s)

Key Findings

#FindingsRiskStatus
1.Incomplete Voucher ReleaseCriticalFixed
2.Overly Centralized FunctionalitiesHighFixed
3.Misconfigured Treasury AllocationMediumFixed
4.Inapplicability of Checks-Effects-Interactions PatternMediumFixed
5.Redundant Duplicate ConstraintsLowFixed
6.Emit events for important operationsLowFixed
7.Insufficient and Inadequate testingLowFixed
8.Inconsistent Parameter Order in ScriptVoucher.mintInformationalFixed
9.Unstructured Enum PlacementInformationalFixed
10.Optimized Enum Indexing for Glass and Voucher TypesInformationalFixed
11.Improper Event Emissions in Contract FunctionsInformationalFixed
12.Optimization of Events’ Naming ConventionsInformationalFixed

Detailed Overview

Critical-risk issues

1. Incomplete Voucher Release

Path: contracts/ScriptTV.sol

Description

The issue of ungraceful handling of voucher association against an unowned _glassID highlights a flaw in the voucher release mechanism. The associatedVouchers mapping is maintained using the msg.sender as the key and the glass ID as the value. When the releaseVoucher function is called, it sets the value in the associatedVouchers mapping for the key msg.sender and the glass ID to false, indicating that the voucher has been released. Since the protocol does not restrict users to associate multiple vouchers to a glass when multiple vouchers have been associated with the same glass and one voucher has been released, the associatedVouchers mapping for that glass will be set to false, leading to the code to revert an AssetNotOwned error when attempting to release another voucher for that glass. This results in an incomplete voucher release and the inability to release the rest of the vouchers for that glass.

Recommendation

Place a constraint in the associateVoucher function to always first check whether the _glassId being provided as a function argument does not have a voucher associated against the msg.sender.

High-risk issues

2. Overly Centralized Functionalities

Path: contracts/ScriptTV.sol

Description

The issue of signature malleability in the case of a compromised owner is a major security concern as it highlights the overly centralized nature of the system's functionalities. The script.TV owner is set during deployment, and a compromised owner could exploit this by equipping gems on the freemium token and removing unlimited SPAY from the earning pool reward and payout since these actions are unchecked. This level of centralization puts the whole protocol at risk, as the compromise of a single owner could bring down the entire system.

Recommendation

To mitigate this risk, it is necessary to implement proper checks and security measures and reduce the number of centralized functionalities in the system. Where a valid data structure is designed to handle.

Medium-risk Issues

3. Misconfigured Treasury Allocation

Path: contracts/ScriptTV.sol

Description: This issue of poorly sanitized treasury configurations highlights a potential flaw in the way funds are being managed within the protocol. An admin with the ability to set the fee can set it to 100, leading to the situation where all user funds are directed into the protocol fee. Furthermore, if the treasuryPercentage is set to 100, it is implied that 100% of the user’s payout/ reward amount passed to the earningPayout function will be sent to the treasury, and none will be sent to the sender. This misconfigured Treasury allocation could result in users not receiving any payouts, which could harm the reputation and sustainability of the project. It is important to thoroughly consider and properly configure the Treasury settings to align with the intended purpose and goals of the project, and to
ensure fairness to all parties involved.

Code:

/**
* @notice change the percentage of treasury cut
* @param _newPercentage new percentage for treasury cut
*/
function setTreasuryPercentage(uint256 _newPercentage) external onlyOwner {
 uint256 oldPercentage = treasuryPercentage;
 if (_newPercentage < 0 || _newPercentage > 100 ether) {
  revert PercentageOutOfRange();
 }
 if (_newPercentage == oldPercentage) {
  revert SameAsOld();
 }
 treasuryPercentage = _newPercentage;
 emit PercentageUpdated(oldPercentage, _newPercentage);
}

Recommendation: Place proper and sensible upper and lower limits for the percentage of the user’s total earnings which the treasury is eligible for in any and all cases.

4. Inapplicability of Checks-Effects-Interactions Pattern

Path: contracts/.sol, contracts/base/.sol

Description

The check-effects-interactions pattern being the first line of defense ensures that checks and validations are performed before any changes or interactions are made with external contracts, reducing the risk of reentrancy in case other contracts are compromised. Failing to properly enforce this pattern can result in unintended consequences, such as potential reentrancy and security breaches. The following of the smart contracts code base is vulnerable to all the attack surfaces caused due to missing implementation of the design pattern:

Recommendation

To avoid these risks, it is essential to properly implement and enforce the checks-effects-interactions pattern in the system to ensure the safe and secure handling of external calls.

Low-risk Issues

5. Redundant Duplicate Constraints

Path: contracts/ScriptTV.sol

Description

The implementation of the setTreasuryPercentage function includes a check to ensure that the provided uint value is never less than 0. This check is unnecessary, as the uint data type in solidity can only contain non-negative values.

The inclusion of this check adds unnecessary complexity to the code and can lead to confusion for developers. Additionally, setTreasury checks for function argument _newTreasury to never be a zero address in two different ways as attached below.

Code

function setTreasuryPercentage(uint256 _newPercentage) external onlyOwner {

 if (_newPercentage < 0 || _newPercentage > 100 ether) {
  revert PercentageOutOfRange();
 }
 function setTreasury(address _newTreasury) external onlyOwner {
  address oldTreasury = treasury;
  require(_newTreasury != address(0), "New Treasury is the zero address");
  if (_newTreasury == address(0)) {
   revert ZeroAddress();
  }

Recommendation

To improve code readability and maintainability, it is recommended to safely remove the above mentioned redundant checks.

6. Emit events for important operations

Path: contracts/ScriptTV.sol

Description

In the current implementation of Script.TV smart contracts, significant blockchain write actions are not recorded as logged events which are imperative to the perpetuating history of blockchain transactions.

Recommendation

It is suggested to emit relevant and self explanatory events with appropriate arguments in all user-facing functionalities.

7. Insufficient and Inadequate testing

Path: contracts/ScriptTV.sol

Description

Throughout the codebase, the testing is insufficient to support the positive and negative test cases from the protocol engineering team. The low level of testing points to a critical lacking in the project as many cases are unexplored.

Recommendation

It is therefore recommended to have a sufficient suite of test cases executed over the production-ready smart contract code to ultimately achieve the satisfaction of all functionality working as expected.

Informational-risk Issues

8. Inconsistent Parameter Order in ScriptVoucher.mint

Path: contracts/base/ScriptVoucher.sol

Description

The function takes in parameters in a non-standard order, where the first parameter is the type of Voucher, and the second parameter is the address. This may cause confusion and increase the likelihood of errors, as it deviates from the standard parameter order used in similar functions.

Recommendation

It is recommended to change the order of the parameters to be (address, type) to align with industry standards and improve overall code clarity and maintainability.

9. Unstructured Enum Placement

Path: contracts/utils/ScriptNFTType.sol

Description

In the ScriptTV protocol, there is an incosistency with the enum data structures. The VoucherType enum is located in the file ScriptVoucher.sol instead of being in a dedicated file for enums. This could lead to overlapping and a lack of structure in the directory.

Recommendation

To avoid this, it is recommended to rename the file utils/ScriptNFTType.sol to utils/enums.sol and move the VoucherType enum to the new file. This will enforce a proper structure of the directory and prevent any potential issues in the future.

10. Optimized Enum Indexing for Glass and Voucher Types

Path: contracts/ScriptTV.sol

Description

To enhance the reliability of the code, it is recommended to utilize a freemium mode for glasses at the last index of the enum definition. This helps ensure that once the available options are exhausted, they will remain unused. The current code blocks make use of a relation between glassType and voucherType, where the glassType is used as +1 and -1 in relation to the voucherType.

Recommendation

To avoid potential arithmetic faults, it is suggested to use a 1-to-1 indexing system for common, rare, and superscript glass and voucher types. This ensures that the enumeration is optimized for improved performance and eliminates the risk of off-by-one issues.

11. Improper Event Emissions in Contract Functions

Path: contracts/ScriptTV.sol

Description

This improvement focuses on optimizing the error handling of stack too deep that occurs in events and improving the efficiency of the code by using scoping blocks. This will ensure that the correct data is emitted onto the blockchain in a clear and concise manner. The current code emits data in the format of RechargeGlasses with the variables msg.sender, discountedAmount, _glassId, and _nonce.

Recommendation

This design flaw can be changed to a more relevant set of variables that are emitted in a more user-friendly format, such as GlassesRecharged with the variables msg.sender, _glassId, and discountedAmount. This change will make the data emitted on the blockchain easier to understand and more readable for users.

12. Optimization of Events’ Naming Conventions

Path: contracts/ScriptTV.sol

Description

In order to improve the readability and transparency of actions performed on the blockchain, it is recommended to rename all events to be self-explanatory and have the parameters ordered in a logical manner. For example, instead of "RechargeGlasses" it could be renamed to "GlassesRecharged", and the parameters could be ordered as (msg.sender, _glassId, discountedAmount) instead of (msg.sender, discountedAmount, _glassId, _nonce).

This will improve the overall clarity and efficiency of the smart contract

Recommendation

It is highly suggested to follow as above.

DISCLAIMER

The smart contracts provided by the client for audit purposes have been thoroughly analyzed in compliance with the global best practices to date w.r.t cybersecurity vulnerabilities and issues in smart contract code, the details of which are enclosed in this report.

This report is not an endorsement or indictment of the project or team, and they do not in any way guarantee the security of the particular object in context. This report is not considered, and should not be interpreted as an influence, on the potential economics of the token, its sale, or any other aspect of the project.

Crypto assets/tokens are the results of the emerging blockchain technology in the domain of decentralized finance and they carry with them high levels of technical risk and uncertainty. No report provides any warranty or representation to any third-Party in any respect, including regarding the bug-free nature of code, the business model or proprietors of any such business model, and the legal compliance of any such business. No third party should rely on the reports in any way, including for the purpose of making any decisions to buy or sell any token, product, service, or another asset. Specifically, for the avoidance of doubt, this report does not constitute investment advice, is not intended to be relied upon as investment advice, is not an endorsement of this project or team, and is not a guarantee as to the absolute security of the project.

Smart contracts are deployed and executed on a blockchain. The platform, its programming language, and other software related to the smart contract can have vulnerabilities that can lead to hacks. The scope of our review is limited to a review of the Solidity code and only the Solidity code we note as being within the scope of our review within this report. The Solidity language itself remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer or any other areas beyond Solidity that could present security risks.

This audit cannot be considered a sufficient assessment regarding the utility and safety of the code, bug-free status, or any other statements of the contract. While we have done our best in conducting the analysis and producing this report, it is important to note that you should not rely on this report only - we recommend proceeding with several independent audits and a public bug bounty program to ensure the security of smart contracts.

Designed & Developed by: 
All rights reserved. Copyright 2023