Abstract
We discovered a critical timing vulnerability in Dx Protocol's token locking mechanism through EVM decompilation. The unlockToken() function contains a race condition that allows attackers to withdraw locked tokens multiple times, potentially draining the entire contract balance. This analysis demonstrates how decompiling unverified contracts can reveal hidden security flaws that put millions of dollars at risk.
We decompiled an unverified Dx Protocol contract on BSC using EVMDecompiler and uncovered a logic flaw in unlockToken().
The issue
If unlockToken() is called before unlockTime, the function still transfers tokens to the caller but leaves isLocked set to true. This allows the caller to repeatedly invoke unlockToken() and withdraw again and again.
Decompiled fragment
function unlockToken(uint256 _tokenId) public {
    require(tokenLocks[msg.sender][_tokenId].isLocked, "Token is already unlocked");
    require(tokenLocks[msg.sender][_tokenId].unlockTime != 0, "Token is not locked");
    if (block.timestamp > tokenLocks[msg.sender][_tokenId].unlockTime) {
        tokenLocks[msg.sender][_tokenId].isLocked = false;
    }
    uint256 amount = tokenLocks[msg.sender][_tokenId].amount;
    require(IERC20(tokenLocks[msg.sender][_tokenId].tokenAddress).balanceOf(address(this)) >= amount, "Not enough balance");
    require(IERC20(tokenLocks[msg.sender][_tokenId].tokenAddress).transfer(msg.sender, amount), "Failed transfer");
    emit Unlocked(msg.sender, tokenLocks[msg.sender][_tokenId].tokenAddress, amount, block.timestamp);
}Why it breaks
isLockedis only flipped tofalsewhenblock.timestamp > unlockTime.- When called earlier, the state remains locked but the transfer proceeds, enabling repeated withdrawals.
 
Impact
Estimated exposure was approximately $5.2M, hidden entirely in bytecode due to the contract being unverified at the time of analysis.
Fix
- Require 
block.timestamp >= unlockTimebefore any transfer logic. - Set 
isLocked = false(and/or zero outunlockTime) before transferring tokens to close re-entry style repeated calls. 
Reproduce
- Load the address in the EVMDecompiler.
 - Locate 
unlockToken()in the decompiled output. - Observe the conditional write to 
isLockedversus the unconditional transfer path. 
Disclosure note: This post is for educational purposes and responsible vulnerability awareness.