Skip to main content
ClaudeWave
Skill55 estrellas del repoactualizado 4mo ago

LSD Token Integration Patterns

>

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/PurpleAILAB/Vigilo /tmp/lsd-token-integration-patterns && cp -r /tmp/lsd-token-integration-patterns/packages/claude/skills/integration-patterns/lsd-tokens ~/.claude/skills/lsd-token-integration-patterns
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

# LSD Token Integration Patterns

This skill provides knowledge for auditing Liquid Staking Derivative token integrations.

## LSD Token Overview

| Token | Protocol | Type | Key Issue |
|-------|----------|------|-----------|
| stETH | Lido | Rebasing | Balance changes daily |
| wstETH | Lido | Non-rebasing | Wrapped stETH |
| rETH | Rocket Pool | Non-rebasing | Exchange rate varies |
| cbETH | Coinbase | Non-rebasing | Exchange rate varies |
| sfrxETH | Frax | Non-rebasing | Yield-bearing |
| frxETH | Frax | 1:1 pegged | No yield |

---

## stETH (Lido) Integration

### Rebasing Behavior

stETH balance increases daily when staking rewards are distributed.

```solidity
// Day 1: User deposits 100 stETH
// Day 2: Balance is now 100.01 stETH (after rebase)
// Day 3: Balance is now 100.02 stETH (after rebase)
```

### Vulnerable Pattern

```solidity
// DANGEROUS: Caches rebasing token balance
contract VulnerableVault {
    mapping(address => uint256) public deposits;

    function deposit(uint256 amount) external {
        stETH.transferFrom(msg.sender, address(this), amount);
        deposits[msg.sender] = amount; // WRONG: Amount will be stale!
    }

    function withdraw() external {
        uint256 amount = deposits[msg.sender];
        stETH.transfer(msg.sender, amount);
        // User loses rebase rewards!
    }
}
```

### Secure Pattern: Use Shares

```solidity
contract SecureVault {
    mapping(address => uint256) public shares;

    function deposit(uint256 stEthAmount) external {
        // Transfer stETH
        uint256 balanceBefore = stETH.balanceOf(address(this));
        stETH.transferFrom(msg.sender, address(this), stEthAmount);
        uint256 received = stETH.balanceOf(address(this)) - balanceBefore;

        // Track shares, not amounts
        uint256 sharesReceived = stETH.getSharesByPooledEth(received);
        shares[msg.sender] += sharesReceived;
    }

    function withdraw() external {
        uint256 userShares = shares[msg.sender];
        shares[msg.sender] = 0;

        // Convert shares back to stETH amount
        uint256 stEthAmount = stETH.getPooledEthByShares(userShares);
        stETH.transfer(msg.sender, stEthAmount);
    }
}
```

### stETH-Specific Functions

```solidity
interface IStETH {
    // Convert stETH amount to shares
    function getSharesByPooledEth(uint256 _ethAmount)
        external view returns (uint256);

    // Convert shares to stETH amount
    function getPooledEthByShares(uint256 _sharesAmount)
        external view returns (uint256);

    // Transfer using shares (avoids rounding)
    function transferShares(address _recipient, uint256 _sharesAmount)
        external returns (uint256);

    // Get shares balance
    function sharesOf(address _account) external view returns (uint256);
}
```

### stETH Transfer Issues

```solidity
// ISSUE: 1-2 wei rounding error on transfers
uint256 balanceBefore = stETH.balanceOf(address(this));
stETH.transferFrom(user, address(this), 1 ether);
uint256 received = stETH.balanceOf(address(this)) - balanceBefore;
// received might be 0.999999999999999998 ether (1-2 wei less)

// SOLUTION: Use transferShares for exact amounts
stETH.transferShares(recipient, sharesAmount);
```

---

## wstETH (Wrapped stETH)

### Why Use wstETH?

wstETH is non-rebasing, making it DeFi-compatible:
- Balance stays constant
- Value increases as stETH/wstETH rate changes
- Easier to integrate than stETH

### Conversion Functions

```solidity
interface IWstETH {
    // Wrap stETH to wstETH
    function wrap(uint256 _stETHAmount) external returns (uint256);

    // Unwrap wstETH to stETH
    function unwrap(uint256 _wstETHAmount) external returns (uint256);

    // Get stETH amount for wstETH
    function getStETHByWstETH(uint256 _wstETHAmount)
        external view returns (uint256);

    // Get wstETH amount for stETH
    function getWstETHByStETH(uint256 _stETHAmount)
        external view returns (uint256);

    // Current exchange rate
    function stEthPerToken() external view returns (uint256);
}
```

### Integration Pattern

```solidity
contract WstETHVault {
    IERC20 public wstETH;

    function deposit(uint256 amount) external {
        // wstETH is non-rebasing, simple tracking works
        wstETH.transferFrom(msg.sender, address(this), amount);
        balances[msg.sender] += amount;
    }

    function getValueInETH(address user) public view returns (uint256) {
        // Convert wstETH balance to ETH value
        uint256 wstBalance = balances[user];
        uint256 stEthAmount = IWstETH(address(wstETH))
            .getStETHByWstETH(wstBalance);
        // stETH is ~1:1 with ETH
        return stEthAmount;
    }
}
```

---

## rETH (Rocket Pool)

### Exchange Rate Behavior

rETH value increases relative to ETH as rewards accrue.

```solidity
interface IRocketTokenRETH {
    // Get ETH value of rETH amount
    function getEthValue(uint256 _rethAmount)
        external view returns (uint256);

    // Get rETH amount for ETH value
    function getRethValue(uint256 _ethAmount)
        external view returns (uint256);

    // Current exchange rate (ETH per rETH)
    function getExchangeRate() external view returns (uint256);
}
```

### Integration Considerations

```solidity
// rETH rate only increases, never decreases (no slashing... yet)
// But rate can be slightly stale

// Getting current value
uint256 ethValue = rETH.getEthValue(rethBalance);

// CAUTION: Exchange rate can be manipulated via Rocket Pool deposits
// Use oracle for price if security-critical
```

---

## cbETH (Coinbase)

### Exchange Rate

```solidity
interface ICbETH {
    // Exchange rate: cbETH per ETH
    function exchangeRate() external view returns (uint256);
}

// Get ETH value
uint256 ethValue = cbEthAmount * cbETH.exchangeRate() / 1e18;
```

### Known Issues

- Exchange rate updated by Coinbase (centralized)
- Rate can be briefly stale
- Consider oracle for critical valuations

---

## sfrxETH (Frax)

### Exchange Rate

```solidit