By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
Liquid staking lets users lock their native proof‑of‑stake tokens (e.g., ETH, DOT, SOL) in a validator and receive a tradable “Liquid Staking Derivative” (LSD) token that represents their stake plus accrued rewards. The derivative can be used instantly in DeFi—swapped on Uniswap, supplied to Aave, or staked in a DAO—while the underlying validator continues to earn consensus rewards.
Real‑world example: Alice deposits 10 ETH into Lido. Lido mints 10 stETH (the LSD) and sends it to Alice. Alice instantly swaps 5 stETH for USDC on Uniswap, uses the remaining 5 stETH as collateral on Aave, and still earns ETH staking rewards behind the scenes.
solidity contract LidoPool is ERC20 { uint256 public totalStaked; // ETH locked in validators function submit() external payable { totalStaked += msg.value; _mint(msg.sender, msg.value); // 1:1 mint of stETH } }
exchangeRate
solidity // non‑rebasing example function exchangeRate() public view returns (uint256) { return totalStaked * 1e18 / totalSupply(); // 1e18 = 1.0 in 18‑decimals }
stETH‑USDC LP
maxFeePerGas
receive()
fallback()
payable
nonReentrant
solidity function withdraw(uint256 amount) external nonReentrant { uint256 ethAmount = amount * exchangeRate() / 1e18; _burn(msg.sender, amount); payable(msg.sender).transfer(ethAmount); }
delegatecall
call
npx hardhat compile
bash npx hardhat run scripts/deploy.js --network goerli
.env
js const router = new ethers.Contract(UNISWAP_ROUTER, routerABI, signer); await router.addLiquidityETH( stETH.address, ethers.utils.parseUnits("5", 18), 0, 0, signer.address, Math.floor(Date.now()/1000)+60*10, { value: ethers.utils.parseEther("5") } );
Contract
Mistake: Minting LSDs 1:1 without tracking total ETH locked. Correction: Keep a totalStaked variable and update it on every deposit/withdrawal; otherwise the exchange rate drifts and rewards are mis‑represented.
totalStaked
Mistake: Using tx.origin for access control (e.g., only the pool owner can call withdraw). Correction: Use msg.sender and OpenZeppelin’s Ownable/AccessControl. tx.origin can be spoofed through a malicious contract, leading to unauthorized withdrawals.
tx.origin
withdraw
msg.sender
Ownable
AccessControl
Mistake: Forgetting to make receive() payable, causing deposits to revert. Correction: Declare receive() external payable {}; this is the fallback that captures plain ETH transfers.
receive() external payable {}
Mistake: Neglecting slashing handling – balances stay unchanged after a validator penalty. Correction: Implement a handleSlashing(uint256 loss) function that reduces totalStaked and proportionally burns LSDs or adjusts the exchange rate.
handleSlashing(uint256 loss)
Mistake: Deploying the pool with an outdated Solidity compiler (e.g., <0.8.0) and missing built‑in overflow checks. Correction: Use pragma solidity ^0.8.20; (or latest stable) to get automatic overflow protection and newer optimizer flags.
pragma solidity ^0.8.20;
handleSlashing
unchecked
Scenario: A contract uses tx.origin to verify that only the original user can withdraw their LSD. Answer: Dangerous – a malicious contract can call the vulnerable contract on behalf of the user, making tx.origin the user’s address and bypassing the check.
Scenario: Your LSD pool’s withdraw function sends ETH before burning the user’s LSD balance. Answer: This opens a re‑entrancy attack; the attacker could re‑enter withdraw and drain more ETH before the balance is reduced.
Scenario: You see an LSD that reports a constant 1:1 exchange rate even after months of staking rewards. Answer: It’s a non‑rebasing token; rewards are reflected via an off‑chain exchangeRate() call, not by increasing the token balance.
exchangeRate()
uint128
uint256
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.