Audit-Driven Fix Secures Liquid Staking Token Against Manipulation

Extropy.IO
5 min read1 day ago

A critical vulnerability was discovered in a recent audit of a project within the LUKSO ecosystem, impacting its liquid staking mechanism. This flaw allowed a malicious actor to manipulate the relationship between staked LYX and the corresponding liquid staking tokens (sLYX), potentially leading to significant financial losses for users. This article delves into the technical details of the vulnerability, its potential impact, and the auditing firm, Extropy, responsible for uncovering it.

Understanding the Intended Functionality

The core functionality revolves around the interaction between two smart contracts: LiquidStakingToken.sol and Vault.sol. The system aims to allow users to stake their LYX tokens and receive sLYX in return, representing their staked assets. The Vault contract manages the staked LYX, while the LiquidStakingToken contract handles the sLYX tokens.

Here’s a breakdown of the intended flow:

1. Deposit: Users deposit LYX into the Vault contract.

2. Stake Transfer: Users then transfer a portion (or all) of their deposited LYX to the LiquidStakingToken contract via the Vault’s transferStake() function. This triggers the LiquidStakingToken.onVaultStakeReceived() function.

3. sLYX Minting: Upon receiving the staked LYX, the LiquidStakingToken contract mints the corresponding amount of sLYX to the user. The amount of sLYX minted is intended to be directly proportional to the amount of LYX staked.

4. Value Calculation: The LiquidStakingToken contract provides functions getNativeTokenValue() and getLiquidStakingTokenValue() to calculate the equivalent LYX value of a given amount of sLYX, and vice-versa. These functions rely on the stakingVault.sharesOf(address(this)) value, which represents the total shares held by the LiquidStakingToken contract within the Vault. The assumption is that this value is equivalent to the total amount of sLYX minted.

Code Snippets of Intended Functionality LiquidStakingToken.sol

/// @dev Calculate the amount of LYX backing an amount of sLYX
function getNativeTokenValue(uint256 sLyxAmount) public view returns (uint256) {
uint256 totalSLYXMinted = stakingVault.sharesOf(address(this));
uint256 lstTokenContractStake = stakingVault.balanceOf(address(this));
return sLyxAmount.mulDiv(lstTokenContractStake, totalSLYXMinted);
}
    /// @dev Calculate the amount of sLYX backed by an amount of LYX
function getLiquidStakingTokenValue(uint256 lyxAmount) public view returns (uint256) {
uint256 lstTokenContractStake = stakingVault.balanceOf(address(this));
uint256 totalSLYXMinted = stakingVault.sharesOf(address(this));
return lyxAmount.mulDiv(totalSLYXMinted, lstTokenContractStake);
}

Code Snippets of Intended Functionality `Vault.sol`

contract Vault{
function sharesOf(address account) external view override returns (uint256) {
return _shares[account];
}
    function balanceOf(address account) public view override returns (uint256) {
return _toBalance(_shares[account]);
}
function deposit(address beneficiary) public payable override whenNotPaused {
// ... (deposit logic) ...
uint256 shares = _toShares(amount);
// ... (share handling logic) ...
_shares[beneficiary] += shares;
totalShares += shares;
totalUnstaked += amount;
emit Deposited(account, beneficiary, amount);
}
}

The Vulnerability: Breaking the Invariant

The core vulnerability lay in the assumption that `stakingVault.sharesOf(address(this))` (in `LiquidStakingToken.sol`) always accurately reflects the total amount of sLYX minted. This invariant is generally true under normal operation, but it could be broken by a malicious actor exploiting a weakness in the Vault contract.

The Vault contract’s `deposit()` function correctly updated the \_shares mapping when LYX was deposited. However, it only accounted for deposits made through its designated `deposit()` function. A malicious user could bypass this mechanism by sending LYX directly to the Vault contract using the `selfdestruct` opcode. This would bypass the intended `deposit()` function logic, including the calculation and updating of shares via \_toShares().

Let’s illustrate the exploit with a potential scenario:

1. Initial Deposit:

  • depositor1 deposits 70 LYX.
  • depositor2 deposits 2 LYX.
  • depositor1 stakes 20 LYX, receiving 20 sLYX. At this point, `sharesOf(liquidStakingTokenContract)` is indeed equal to the total sLYX minted.

2. Malicious Attack:

  • A malicious user could create a contract, fund it with a small amount of gwei (e.g., a few wei), and then call `selfdestruct()` on their contract, sending the gwei directly to the Vault contract’s address. Critically, this bypassed the `receive()` function and therefore the `deposit()` function entirely. No shares were updated because the deposit logic within `deposit()` was not executed.

3. Rebalance (Triggered):

  • The `rebalance()` function is a complex mechanism within the Vault contract, typically called periodically by an oracle. It plays a crucial role in maintaining the correct accounting of assets within the Vault. The function recomputes and updates several internal variables, including `totalStaked`, `totalUnstaked`, `totalPendingWithdrawal`, `totalClaimable`, and `totalFees`. It does this by considering the current balance of the Vault contract, accounting for completed and pending withdrawals, and distributing rewards. Crucially, it relies on the `address(this).balance` which now includes the gwei sent by the attacker, while the \_shares mapping remains unchanged. This discrepancy creates an inconsistency that the rebalance() function cannot fully rectify.

4. Subsequent Stake:

  • depositor2 stakes 1 LYX, expecting to receive 1 sLYX. However, because the malicious actor’s gwei transfer increased the totalAssets in the Vault without a corresponding increase in `totalShares`, the \_toShares() calculation will be skewed. depositor2 will receive an incorrect amount of sLYX, likely less than expected.

Impact and remediation

This vulnerability allowed a malicious actor to manipulate the exchange rate between LYX and sLYX. By injecting a small amount of gwei directly into the Vault, they could have inflated the `totalAssets` without increasing the `totalShares` held by the `LiquidStakingToken` contract. This, in turn, distorted the calculations in `getNativeTokenValue()` and `getLiquidStakingTokenValue()`, potentially causing users to receive less sLYX

than they should when staking, or less LYX when redeeming sLYX.

To address this vulnerability, the project developers implemented a crucial change to the SLYX token contract. Instead of basing the minting of sLYX tokens on the amount of staked LYX, the contract now directly utilizes the Vault’s shares. This fundamental shift ensures that the number of sLYX tokens minted accurately reflects the user’s share entitlement, regardless of any manipulations to the underlying LYX balance.

This change was acceptable because the primary function of minted sLYX tokens is to represent the user’s share ownership, not the precise amount of LYX staked. By aligning the minting process with the Vault’s share mechanism, the contract effectively mitigated the risk of manipulation and maintains the integrity of the liquid staking system.

Conclusion

The vulnerability discovered by Extropy in the LUKSO ecosystem project demonstrates the critical importance of secure smart contract development and thorough auditing. The ability to manipulate the LYX/sLYX exchange rate through a relatively simple exploit possessed a significant risk to users. This incident serves as a reminder that even seemingly robust systems can contain hidden vulnerabilities. Thanks to Extropy’s diligent work, this vulnerability was identified and can be addressed before it leads to any actual losses. It is crucial for projects in the blockchain space to prioritize security and engage reputable auditing firms to ensure the safety of user funds and the integrity of their platforms. The complex interactions between different contracts and the potential for unintended consequences require a meticulous approach to development and security analysis. Only through such rigorous practices can we build a truly secure and trustworthy decentralized ecosystem.

Since 2017, Extropy has been at the forefront of blockchain security, auditing smart contracts across Ethereum and Zero-Knowledge (ZK) protocols. We have collaborated with leading ecosystems, including Base, Starknet, and MINA, ensuring their smart contracts are resilient, efficient, and secure.

We specialize in DeFi, on-chain games, and ZK applications, leveraging formal verification, static analysis, and deep manual reviews to uncover vulnerabilities before they become exploits. Whether you’re working with Solidity, Rust, Cairo, or zkVMs, our collaborative approach ensures your project meets the highest security standards.

Website:

Get in touch today — let’s build safer smart contracts together!

--

--

Extropy.IO
Extropy.IO

Written by Extropy.IO

Oxford-based blockchain and zero knowledge consultancy and auditing firm

No responses yet