Audit Anomalies Archive — Issue#7

Zuhaib Mohammed
3 min readNov 14, 2023

--

About the Protocol

I had the opportunity to audit a bridge designed for the transfer of native assets between a blockchain, which we’ll refer to as TestChain, and the Ethereum Mainnet. The native token is represented by $TEST. Essentially, there exists an ERC20 version of $TEST deployed on the Ethereum Mainnet, and the bridge facilitates users in transferring the native token seamlessly between the Mainnet and TestChain in both directions.

For simplicity, let’s assume the contract comprises two functions:

1. depositNativeToken: Users deposit the native $TEST token on the TestChain and await confirmation from the signer to sign the transaction, facilitating the withdrawal on the ERC20 Mainnet.

2. emergencyWithdrawNative: This function serves emergency purposes. In the event of a potential security breach or hack, the owner can utilize this function to completely remove the liquidity of the $TEST token, preventing potential financial losses.

selfdestruct ??

selfdestruct in EVM chains is a feature allowing a smart contract to delete itself from the blockchain. It involves triggering the contract’s own selfdestruct function or that of another contract, specifying a destination address to receive the remaining Ether. Once invoked, selfdestruct permanently removes the contract’s code and storage from the Ethereum state, making it an irreversible action. A sample example is shown below.

pragma solidity ^0.8.0;

contract SelfDestructExample {
address public owner;

// Constructor to set the owner when the contract is deployed
constructor() {
owner = msg.sender;
}

// Function to destroy the contract and send remaining funds to a specified address
function destroyContract(address payable recipient) external onlyOwner {
// Ensure a valid recipient address is provided
require(msg.sender == owner, "Only the owner can call this function")
require(recipient != address(0), "Invalid recipient address");

// Transfer any remaining Ether to the specified address
selfdestruct(recipient);
}
}

The Issue at Hand

Analyzing the code snippet below should provide insights into the potential exploitability of the code.

function depositNativeToken(
address receiver
) external payable nonReentrant {
//SOME CODE

// Check if token balance has increased by amount
if (address(this).balance - msg.value != nativeTokenBalance)
revert InvalidTransfer();

// Increase native token balance
nativeTokenBalance += msg.value;

//REST OF THE CODE
}

function emergencyWithdrawNative() external onlyOwner {
uint256 balance = address(this).balance;

// Transfer native tokens to the admin
(bool success, ) = payable(_msgSender()).call{value: balance}("");
if (!success) revert InvalidTransfer();

// Decrease native token balance
nativeTokenBalance -= balance;

//REST OF THE CODE
}

If you didn’t identify the issue, it stems from the use of nativeTokenBalance to track balances.

In the case of depositNativeToken, the check address(this).balance - msg.value != nativeTokenBalance could easily be manipulated to revert if a user forcefully sends ether to the contract via selfdestruct. This action could block honest users from using the bridge and lead to a Denial-of-Service (DoS) scenario.

Similarly, in the context of emergencyWithdrawNative, the check nativeTokenBalance -= balance; may underflow if a user forcefully sends ether to the contract via selfdestruct, causing a Denial-of-Service (DoS) situation for the onlyOwner attempting to retrieve funds in case of an emergency.

For a practical demonstration of this issue via Foundry, additional details can be found in this link.

Thanks for Reading !

Connect with me: https://linktr.ee/zuhaib44

--

--