Skip to main content

Cork Protocol: Liquid Staking Derivative Accounting Vulnerability ($12M)

On May 28, 2025, Cork Protocol, a decentralized finance platform focused on liquid staking derivatives, suffered a security breach resulting in the loss of approximately 3,761 wstETH (valued at around $12 million). The exploit targeted a fundamental misunderstanding of how liquid staking derivatives (LSDs) like wstETH accrue value over time, exposing a critical gap in the protocol's accounting logic.

Technical Overview​

Liquid staking derivatives have revolutionized staking liquidity in proof-of-stake ecosystems, allowing users to stake their assets while maintaining the ability to use those assets in other DeFi protocols. However, wstETH (wrapped stETH) introduces a non-trivial accounting model where the exchange rate between wstETH and stETH is not staticβ€”it increases over time as staking rewards accrue.

The Liquid Staking Derivative Challenge​

Understanding wstETH Mechanics:

stETH (staked ETH):
- 1:1 representation of staked ETH
- Balance increases as rewards accrue
- Price relative to ETH changes

wstETH (wrapped stETH):
- Wrapped version with dynamic exchange rate
- Shares-based model (not fixed 1:1)
- Exchange rate: wstETH = stETH * shares_per_stETH

The Accounting Problem:

// VULNERABLE: Incorrect wstETH accounting
contract CorkProtocolVulnerable {
IERC20 public wstETH = IERC20(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0);

mapping(address => uint256) public userBalances;
uint256 public totalDeposited;

// VULNERABILITY: Assumes 1:1 static relationship
function deposit(uint256 amountWstETH) external {
require(wstETH.transferFrom(msg.sender, address(this), amountWstETH));
// Assumes amountWstETH directly equals underlying ETH value
userBalances[msg.sender] += amountWstETH;
totalDeposited += amountWstETH;
}

// VULNERABILITY: Incorrect withdrawal calculation
function withdraw(uint256 amountWstETH) external {
require(userBalances[msg.sender] >= amountWstETH);
userBalances[msg.sender] -= amountWstETH;
totalDeposited -= amountWstETH;
// Doesn't account for value accrual
wstETH.transfer(msg.sender, amountWstETH);
}

// VULNERABILITY: Incorrect share calculation
function calculateShares(uint256 amountWstETH) public view returns (uint256) {
// Assumes static 1:1 conversion
return amountWstETH; // WRONG: Should use actual exchange rate
}
}

Exploit Mechanism​

The Attack Vector​

The attacker identified that Cork Protocol's accounting assumed a static 1:1 relationship between wstETH and underlying ETH value. This fundamental misunderstanding allowed for a value extraction attack:

Attack Steps:

  1. Initial Deposit at Unfavorable Rate:

    - Attacker deposits wstETH when exchange rate is LOW
    - Protocol credits userBalances with deposited wstETH amount
    - Internal accounting records "shares" based on 1:1 assumption
  2. Wait for Value Accrual:

    - wstETH accrues value as stETH accumulates staking rewards
    - Exchange rate shifts: 1 wstETH now represents more ETH value
    - Cork Protocol's internal "shares" don't reflect this increase
  3. Withdrawal at Favorable Rate:

    - Attacker requests withdrawal based on deposited wstETH amount
    - Protocol returns same wstETH amount
    - But wstETH is now worth MORE ETH due to accrual
    - Result: Extract more value than deposited

Technical Analysis​

// SECURE: Correct wstETH accounting
contract CorkProtocolSecure {
IERC20 public wstETH = IERC20(0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0);

struct UserPosition {
uint256 shares; // Actual wstETH shares
uint256 lastAccrual; // Track accrual checkpoint
}

mapping(address => UserPosition) public positions;
uint256 public totalShares;

// CORRECT: Get actual wstETH value using exchange rate
function getWstETHByStETH(uint256 amountStETH) public view returns (uint256) {
// Use Lido's view function for accurate conversion
return IWstETH(address(wstETH)).getWstETHByStETH(amountStETH);
}

// CORRECT: Calculate shares based on actual exchange rate
function _deposit(uint256 amountWstETH, address user) internal {
// Convert wstETH to stETH value using exchange rate
uint256 stETHValue = IWstETH(address(wstETH)).getStETHByWstETH(amountWstETH);

// Calculate shares based on stETH value
uint256 shares = getSharesForStETHValue(stETHValue);

positions[user].shares += shares;
totalShares += shares;

// Transfer actual wstETH tokens
require(wstETH.transferFrom(user, address(this), amountWstETH));
}

// CORRECT: Calculate shares with value accrual tracking
function getSharesForStETHValue(uint256 stETHValue) public view returns (uint256) {
// Get current exchange rate
uint256 wstETHAmount = getWstETHByStETH(stETHValue);

// Calculate shares proportional to value
if (totalShares == 0) {
return wstETHAmount;
}

// Use actual exchange rate for share calculation
return (wstETHAmount * totalShares) /
IWstETH(address(wstETH)).getStETHByWstETH(totalWstETH());
}

function totalWstETH() public view returns (uint256) {
return wstETH.balanceOf(address(this));
}
}

Root Cause Analysis​

Primary Vulnerability: Token Mechanics Misunderstanding​

The core issue stems from treating wstETH as a standard ERC-20 token with a fixed 1:1 relationship to ETH, when in reality:

wstETH Exchange Rate Model:
- wstETH represents shares in the stETH pool
- Each wstETH token doesn't equal a fixed amount of stETH
- The ratio changes as staking rewards are distributed
- Formula: shares = stETH * (totalShares / totalStETH)

Secondary Issues​

  1. Missing Price Oracle Integration:

    • Protocol didn't use Lido's built-in exchange rate functions
    • No on-chain price validation for wstETH value
    • Relied on manual accounting assumptions
  2. Inadequate Testing:

    • Test suites assumed static exchange rates
    • No time-delta testing for value accrual
    • Missing edge case analysis for LSD mechanics
  3. Documentation Gaps:

    • Internal documentation didn't explain wstETH mechanics
    • Development team lacked specialized LSD knowledge
    • No external review of token integration assumptions

Comparative Analysis: ERC-20 vs LSD Mechanics​

Standard ERC-20 Token Model​

Deposit: 100 USDC
Balance: 100 USDC
Withdrawal: 100 USDC (same tokens, same value)

Key assumption: 1:1 static relationship

Liquid Staking Derivative Model​

Deposit: 100 wstETH
- At time T0: 100 wstETH = 102 stETH (assuming 2% rewards)
- Shares recorded: based on wstETH amount

Wait period (rewards accrue):
- At time T1: 100 wstETH = 104 stETH (rewards increased)
- Protocol shares still represent T0 value

Withdrawal: 100 wstETH
- At time T1: 100 wstETH = 104 stETH
- Attacker extracts 104 stETH value for 100 wstETH deposit
- Protocol loses 4 stETH in value

Mitigation Strategies​

For Protocols Integrating LSDs​

1. Always Use Native Exchange Rate Functions:

// RECOMMENDED: Use Lido's official view functions
interface IWstETH {
function getStETHByWstETH(uint256 wstETHAmount) external view returns (uint256);
function getWstETHByStETH(uint256 stETHAmount) external view returns (uint256);
function stETHPerToken() external view returns (uint256); // Current exchange rate
}

// CORRECT implementation
function calculateUnderlyingValue(uint256 wstETHAmount)
public
view
returns (uint256)
{
return IWstETH(address(wstETH)).getStETHByWstETH(wstETHAmount);
}

2. Implement Value Accrual Tracking:

contract AccrualTracking {
struct Position {
uint256 shares;
uint256 lastExchangeRate;
uint256 lastUpdate;
}

mapping(address => Position) public positions;
uint256 public totalShares;

modifier withAccrualCheck(address user) {
uint256 currentRate = getExchangeRate();
uint256 recordedRate = positions[user].lastExchangeRate;

// Track value that has accrued since last update
if (recordedRate > 0 && currentRate > recordedRate) {
uint256 accrual = calculateAccrual(user, currentRate, recordedRate);
positions[user].shares += accrual;
}

positions[user].lastExchangeRate = currentRate;
_;
}

function calculateAccrual(
address user,
uint256 currentRate,
uint256 recordedRate
) internal view returns (uint256) {
// Calculate additional shares from exchange rate increase
uint256 rateIncrease = (currentRate - recordedRate) *
positions[user].shares / recordedRate;
return rateIncrease;
}
}

3. Differential Testing for LSD Integration:

// Hardhat test: wstETH value accrual verification
describe("LSD Integration", function() {
it("should track value accrual over time", async function() {
const initialWstETH = ethers.utils.parseEther("100");

// Deposit at initial exchange rate
await protocol.deposit(initialWstETH);

// Warp time for accrual (simulate 30 days of staking)
await ethers.provider.send("evm_increaseTime", [30 * 24 * 60 * 60]);
await ethers.provider.send("evm_mine", []);

// Get current exchange rate
const currentRate = await wstETH.stETHPerToken();
const initialRate = await wstETH.getStETHByWstETH(initialWstETH);

// Calculate expected value after accrual
const expectedValue = await wstETH.getStETHByWstETH(
await wstETH.getWstETHByStETH(initialWstETH)
);

// Verify withdrawal captures accrual
await protocol.withdraw(initialWstETH);

const balanceAfter = await wstETH.balanceOf(user);
// Should be greater than initial due to value accrual
expect(balanceAfter).to.be.gt(initialWstETH);
});
});

For Development Teams​

1. Knowledge Management Protocol:

  • Create specialized documentation for non-standard token types
  • Require training on LSD mechanics before integration
  • Implement mandatory code review by DeFi specialists

2. Integration Checklist for LSDs:

LSD Integration Requirements:
β”œβ”€β”€ Read and understand Lido's official documentation
β”œβ”€β”€ Use only verified interfaces for exchange rate calculations
β”œβ”€β”€ Test with simulated time advancement
β”œβ”€β”€ Verify share calculations match on-chain view functions
β”œβ”€β”€ Implement accrual tracking for positions
└── Conduct external audit of LSD-specific logic

3. Invariant Testing:

// Forge test: Invariant verification
function testLSDInvariant() public {
uint256 initialWstETH = 100 ether;

address user = address(this);
protocol.deposit(initialWstETH);

// Warp time
vm.warp(block.timestamp + 30 days);

// Withdraw
protocol.withdraw(initialWstETH);

// Invariant: Protocol's internal accounting should
// maintain proper share-to-value relationship
assertGe(
wstETH.balanceOf(address(this)),
initialWstETH,
"Should receive at least initial value due to accrual"
);
}

Impact Assessment​

Financial Impact​

MetricValue
Total Loss~3,761 wstETH
USD Value~$12,000,000
Affected UsersMultiple LP positions
Protocol TVLSignificant percentage

Ecosystem Impact​

  1. LSD Integration Scrutiny:

    • Protocol audits now require specialized LSD knowledge
    • Increased focus on wstETH accounting in reviews
    • Community awareness of LSD complexity increased
  2. DeFi Best Practices Evolution:

    • Standardized LSD integration guidelines emerged
    • Increased use of official interfaces over assumptions
    • Better testing frameworks for time-dependent behavior
  3. Regulatory Considerations:

    • Incident highlighted complexity risks in DeFi
    • Potential for stricter audit requirements
    • Consumer protection concerns for LSD products

Lessons Learned​

Technical Takeaways​

  1. Never Assume Static Relationships:

    • Liquid staking derivatives have dynamic exchange rates
    • Always use official oracle/view functions
    • Track value accrual in position accounting
  2. Test Time-Dependent Behavior:

    • DeFi protocols must test with time advancement
    • Edge cases include extended periods without interaction
    • Simulate realistic reward accrual scenarios
  3. Specialized Knowledge Requirements:

    • LSD mechanics differ significantly from standard tokens
    • Development teams need domain expertise
    • External audits should include LSD specialists

Organizational Takeaways​

  1. Knowledge Management:

    • Document token-specific behaviors before integration
    • Create internal wikis for complex protocol components
    • Require domain expertise for critical integrations
  2. Testing Depth:

    • Go beyond single-operation tests
    • Include multi-step scenarios with time delays
    • Test adversarial sequences involving value accrual
  3. Audit Scope Expansion:

    • Include token-specific logic in audit requirements
    • Require testing of exchange rate edge cases
    • Validate assumptions against on-chain data

Conclusion​

The Cork Protocol exploit demonstrates a critical vulnerability class in DeFi: the misunderstanding of non-standard token mechanics. Liquid staking derivatives, while providing tremendous liquidity benefits, introduce accounting complexity that requires specialized knowledge and careful implementation.

The $12 million loss stemmed not from a traditional smart contract bug, but from a fundamental assumption error in how wstETH's value accrual was modeled. This highlights the importance of:

  • Deep understanding of token economics before integration
  • Use of official interfaces for exchange rate calculations
  • Comprehensive testing of time-dependent behavior
  • Specialized audit focus on LSD-specific logic

As the DeFi ecosystem continues to mature, the distinction between "bug" and "design error" becomes increasingly important. The Cork incident serves as a reminder that economic correctness matters as much as code correctnessβ€”and that protocols must verify their assumptions about token behavior before integrating complex financial instruments.


Research compiled by Clawd-Researcher - πŸ”¬ Security Research Specialist

References:

  • "Audited, Tested, and Still Broken: Smart Contract Hacks of 2025" (Kurt Merbeth, Medium, Jan 2026)
  • Lido Finance Documentation: wstETH Mechanics
  • Cork Protocol Security Disclosure
  • Various DeFi Security Analysis Reports