Module 6 - About Liquid Staking Adapters
Adapters for Liquid Staking
This Module is Useful for Understanding the StakeStone and Lido Adapters which are important for Learning about Liquid Staking Adapters. Refer this Repository for Smart Contracts.
StakeStone Adapter
The StakeStoneStakeEth
Smart Contract allows users to Stake ETH and Receive STONE Tokens on the StakeStone Platform. This Tutorial provides a Comprehensive Guide to Understanding and using the StakeStoneStakeEth
Contract.
-
Contract Structure - The
StakeStoneStakeEth
Smart Contract allows users to Stake their ETH in the StakeStone Vault to Receive STONE Tokens. It also supports cross-chain functionality to Deposit received STONE Tokens on another Chain using LayerZero OFT.a) stone - The Address of the STONE token.
b) stoneVault - The Interface to Interact with the StakeStone Vault.
address public immutable stone;IStoneVault public immutable stoneVault;
-
Functions -
a) constructor() - The Constructor initializes the Contract with Native Token, Wrapped Native Token, StakeStone Vault, and STONE Token Addresses.
constructor(address __native,address __wnative,address __stoneVault,address __stone) RouterIntentEoaAdapterWithoutDataProvider(__native, __wnative) {stoneVault = IStoneVault(__stoneVault);stone = __stone;}b) name() - The
name
function returns the name of the Adapter.function name() public pure override returns (string memory) {return "StakeStoneStake";}c) execute() - The
execute
function Performs the Staking Operation by Parsing Input Data, handling ETH Transfers, and Calling the_stake
function.function execute(bytes calldata data) external payable override returns (address[] memory tokens) {(address _recipient, uint256 _amount, uint16 dstEid, bytes memory crossChainData) = parseInputs(data);uint256 nativeFee = 0;if (dstEid != 0) nativeFee = abi.decode(crossChainData, (uint256));if (address(this) == self()) {require(msg.value == _amount,Errors.INSUFFICIENT_NATIVE_FUNDS_PASSED);_amount = msg.value - nativeFee;} else if (_amount == type(uint256).max)_amount = address(this).balance - nativeFee;else {_amount = _amount - nativeFee;}bytes memory logData;(tokens, logData) = _stake(_recipient, _amount, dstEid, crossChainData);emit ExecutionEvent(name(), logData);return tokens;}d) _stake() - The
_stake
function Performs the Actual Staking Operation with thestoneVault
and handles cross-chain Operations if needed.function _stake( address _recipient, uint256 _amount, uint16 dstEid, bytes memory crossChainData) internal returns (address[] memory tokens, bytes memory logData) {uint256 receivedStone = stoneVault.deposit{value: _amount}();if (dstEid != 0) {depositLzOFT(dstEid, crossChainData, receivedStone, _recipient);} else {if (_recipient != address(this))withdrawTokens(stone, _recipient, receivedStone);}tokens = new address[](2);tokens[0] = native();tokens[1] = stone;logData = abi.encode(_recipient, _amount, receivedStone);}e) depositLzOFT() - The
depositLzOFT
function handles cross-chain Transfers using LayerZero OFT.function depositLzOFT(uint16 dstEid, bytes memory crossChainData, uint256 amount, address recipient) public {(uint256 nativeFee, address refundAddress) = abi.decode(crossChainData,(uint256, address));ILzOft(stone).sendFrom{value: nativeFee}(address(this), dstEid, abi.encodePacked(recipient), amount, payable(refundAddress), address(0) , hex"");}f) parseInputs() - The
parseInputs
function Decodes the Input Data.function parseInputs(bytes memory data) public pure returns (address, uint256, uint16, bytes memory) {return abi.decode(data, (address, uint256, uint16, bytes));}g) receive() - The
receive
function allows the Contract to Accept ETH Deposits.receive() external payable {} -
Usage - To use the
StakeStoneStakeEth
Contract -- Deploy the Contract with the Native Token, Wrapped Native Token,
stoneVault
, and STONE Token Addresses. - Call the
execute
function with the appropriate Input Data to Stake ETH and Receive STONE Tokens.
- Deploy the Contract with the Native Token, Wrapped Native Token,
// Example data to be passed to the execute function (address recipient, uint256 amount, uint16 dstEid, bytes memory crossChainData) = ( address(0x...), 1000000000000000000, // 1 ETH in wei 0, // Destination chain ID (0 for same chain) new bytes(0) // No cross-chain data );
bytes memory data = abi.encode(recipient, amount, dstEid, crossChainData);
stakeStoneStakeEth.execute{value: amount}(data);
- Error Handling - The
StakeStoneStakeEth
Contract uses the Errors library to handle various error Scenarios, such as Insufficient Native Funds.
Conclusion
The StakeStoneStakeEth
Smart Contract Simplifies Staking ETH in Exchange for STONE Tokens on the StakeStone Platform. By following this Tutorial, you should be able to Understand and Utilize the Contract effectively.
Lido Adapter
The LidoStakeEth
Smart Contract. This Contract allows users to Stake their ETH to Receive stETH on Lido and optionally Bridge the Staked Assets to various Layer 2 Solutions. This Guide will Walk you through the Structure, functionality, and Key Aspects of the Contract.
-
Contract Structure - The
LidoStakeEth
Contract enables Staking ETH to Receive stETH via the Lido Protocol on the Ethereum Network. Additionally, it supports Bridging the received stETH to other Layer 2 networks or Sidechains like Arbitrum, Optimism, Base, Linea, Mantle, zkSync, and Scroll.a) State Variables - The Contract Defines several State Variables to Manage Addresses of various Bridges, Gateways, and Token Contracts.
```soladdress public immutable lidoStETH;address public immutable lidoWstEth;address public immutable referralId;ILidoArbitrumBridge public immutable arbitrumGateway;ILidoOptBaseMan public immutable baseGateway;ILidoLineaBridge public immutable lineaGateway;ILidoOptBaseMan public immutable mantleGateway;ILidoOptBaseMan public immutable optimismGateway;ILidoZkSyncBridge public immutable zksyncGateway;ILidoScrollBridge public immutable scrollGateway;IScrollMessageQueue public immutable scrollMessageQueue;address public immutable lidoWstEthOptimism;address public immutable lidoWstEthBase;address public immutable lidoWstEthMantle;IZkSync public immutable zkSyncBridge;```b) Chain IDs - The Contract also Defines Constants for various Chain IDs -
```soluint256 public constant ARBITRUM_CHAIN_ID = 42161;uint256 public constant OPTIMISM_CHAIN_ID = 10;uint256 public constant BASE_CHAIN_ID = 8453;uint256 public constant LINEA_CHAIN_ID = 59144;uint256 public constant MANTLE_CHAIN_ID = 5000;uint256 public constant ZKSYNC_CHAIN_ID = 324;uint256 public constant SCROLL_CHAIN_ID = 534352;``` -
Functions -
a) constructor() - The Constructor initializes the Contract with Addresses for the Native and Wrapped Native Tokens, Lido Token Contracts, various Gateways, and Bridges.
```solconstructor(address __native,address __wnative,address __lidoStETH,address __lidoWstETH,address __referralId,address __arbitrumGateway,address __baseGateway,address __lineaGateway,address __mantleGateway,address __optimismGateway,address __zksyncGateway,address __scrollGateway,address __scrollMessageQueue,address __lidoWstEthOptimism,address __lidoWstEthBase,address __lidoWstEthMantle) RouterIntentEoaAdapterWithoutDataProvider(__native, __wnative) {lidoStETH = __lidoStETH;lidoWstEth = __lidoWstETH;referralId = __referralId;arbitrumGateway = ILidoArbitrumBridge(__arbitrumGateway);baseGateway = ILidoOptBaseMan(__baseGateway);lineaGateway = ILidoLineaBridge(__lineaGateway);mantleGateway = ILidoOptBaseMan(__mantleGateway);optimismGateway = ILidoOptBaseMan(__optimismGateway);zksyncGateway = ILidoZkSyncBridge(__zksyncGateway);scrollGateway = ILidoScrollBridge(__scrollGateway);scrollMessageQueue = IScrollMessageQueue(__scrollMessageQueue);lidoWstEthOptimism = __lidoWstEthOptimism;lidoWstEthBase = __lidoWstEthBase;lidoWstEthMantle = __lidoWstEthMantle;zkSyncBridge = zksyncGateway.zkSync();}```b) name() - Returns the name of the Contract.
```solfunction name() public pure override returns (string memory) {return "LidoStakeEth";}```c) execute() - Executes the Staking and Bridging Process based on the Input Data.
```solfunction execute(bytes calldata data) external payable override returns (address[] memory tokens) {(address _recipient,uint256 _amount,uint256 _bridgeChainId,bytes memory _bridgeData) = parseInputs(data);uint256 _bridgeFee = getBridgeFee(_bridgeChainId, _bridgeData);if (address(this) == self()) {require(msg.value == _amount + _bridgeFee,Errors.INSUFFICIENT_NATIVE_FUNDS_PASSED);} else if (_amount == type(uint256).max)_amount = address(this).balance - _bridgeFee;else _amount = _amount - _bridgeFee;bytes memory logData;(tokens, logData) = _stake(_recipient,_amount,_bridgeChainId,_bridgeFee,_bridgeData);emit ExecutionEvent(name(), logData);return tokens;}```d) _stake() - Handles the Staking Logic and Determines whether to Bridge the Staked Tokens to another Chain.
```solfunction _stake(address _recipient,uint256 _amount,uint256 _bridgeChainId,uint256 _bridgeFee,bytes memory _bridgeData) internal returns (address[] memory tokens, bytes memory logData) {ILidoStakeEth(lidoStETH).submit{value: _amount}(referralId);bytes memory data;if (_bridgeChainId == 0) {tokens = new address ;tokens[0] = native();tokens[1] = lidoStETH;data = abi.encode(0,withdrawTokens(lidoStETH, _recipient, type(uint256).max));} else {tokens = new address ;tokens[0] = native();tokens[1] = lidoStETH;tokens[2] = lidoWstEth;data = _bridge(_bridgeChainId, _bridgeFee, _bridgeData);}logData = abi.encode(_recipient, _amount, data);}```e) parseInputs() - Parses the Input Data to extract Recipient Address, Amount, Bridge Chain ID, and Bridge Data.
```solfunction parseInputs(bytes memory data) public pure returns (address, uint256, uint256, bytes memory) {return abi.decode(data, (address, uint256, uint256, bytes));}```f) _bridge() - Handles the Bridging Logic for various Chains.
```solfunction _bridge(uint256 chainId,uint256 bridgeFee,bytes memory bridgeData) internal returns (bytes memory) {if (chainId == ARBITRUM_CHAIN_ID) {return _bridgeToArbitrum(bridgeFee, bridgeData);} else if (chainId == OPTIMISM_CHAIN_ID) {return _bridgeToOptBaseMan(OPTIMISM_CHAIN_ID, optimismGateway, lidoWstEthOptimism, bridgeData);} else if (chainId == BASE_CHAIN_ID) {return _bridgeToOptBaseMan(BASE_CHAIN_ID, baseGateway, lidoWstEthBase, bridgeData);} else if (chainId == MANTLE_CHAIN_ID) {return _bridgeToOptBaseMan(MANTLE_CHAIN_ID, mantleGateway, lidoWstEthMantle, bridgeData);} else if (chainId == LINEA_CHAIN_ID) {return _bridgeToLinea(bridgeData);} else if (chainId == ZKSYNC_CHAIN_ID) {return _bridgeToZkSync(bridgeFee, bridgeData);} else if (chainId == SCROLL_CHAIN_ID) {return _bridgeToScroll(bridgeFee, bridgeData);} else revert(Errors.INVALID_BRIDGE_CHAIN_ID);}```g) _bridgeToArbitrum() - Handles the Bridging Logic to Arbitrum.
```solfunction _bridgeToArbitrum(uint256 bridgeFee,bytes memory data) internal returns (bytes memory) {(address recipient_,uint256 amount_,uint256 maxGas_,uint256 gasPriceBid_,uint256 maxSubmissionCost_) = abi.decode(data, (address, uint256, uint256, uint256, uint256));if (amount_ == type(uint256).max)amount_ = IERC20(lidoStETH).balanceOf(address(this));amount_ = convertToWstEth(amount_);IERC20(lidoWstEth).safeIncreaseAllowance(address(arbitrumGateway),amount_);bytes memory returnData = arbitrumGateway.outboundTransfer{value: bridgeFee}(lidoWstEth,recipient_,amount_,maxGas_,gasPriceBid_,abi.encode(maxSubmissionCost_, hex""));uint256 retryableTicketId = abi.decode(returnData, (uint256));return abi.encode(ARBITRUM_CHAIN_ID, amount_, retryableTicketId);}```h) _bridgeToOptBaseMan() - Handles the Bridging Logic to Optimism, Base, and Mantle.
```solfunction _bridgeToOptBaseMan(uint256 chainId,ILidoOptBaseMan gateway,address wstEthDestChain,bytes memory data) internal returns (bytes memory) {(address recipient_,uint256 amount_,uint32 l2Gas_,bytes memory data_) = abi.decode(data, (address, uint256, uint32, bytes));if (amount_ == type(uint256).max)amount_ = IERC20(lidoStETH).balanceOf(address(this));amount_ = convertToWstEth(amount_);IERC20(lidoWstEth).safeIncreaseAllowance(address(gateway), amount_);gateway.depositERC20To(lidoWstEth,wstEthDestChain,recipient_,amount_,l2Gas_,data_);return abi.encode(chainId, amount_);}```i) _bridgeToLinea() - Handles the Bridging Logic to Linea.
```solfunction _bridgeToLinea(bytes memory data) internal returns (bytes memory) {(address recipient_, uint256 amount_) = abi.decode(data,(address, uint256));if (amount_ == type(uint256).max)amount_ = IERC20(lidoStETH).balanceOf(address(this));amount_ = convertToWstEth(amount_);IERC20(lidoWstEth).safeIncreaseAllowance(address(lineaGateway),amount_);lineaGateway.bridgeToken(lidoWstEth, amount_, recipient_);return abi.encode(LINEA_CHAIN_ID, amount_);}```j) _bridgeToZkSync() - Handles the Bridging Logic to zkSync.
```solfunction _bridgeToZkSync(uint256 bridgeFee,bytes memory data) internal returns (bytes memory) {(address recipient_,address refundRecipient_,uint256 amount_,uint256 _l2TxGasLimit) = abi.decode(data, (address, address, uint256, uint256));if (amount_ == type(uint256).max)amount_ = IERC20(lidoStETH).balanceOf(address(this));amount_ = convertToWstEth(amount_);IERC20(lidoWstEth).safeIncreaseAllowance(address(zksyncGateway),amount_);bytes32 l2TxHash = zksyncGateway.deposit{value: bridgeFee}(recipient_,lidoWstEth,amount_,_l2TxGasLimit,800,refundRecipient_);return abi.encode(ZKSYNC_CHAIN_ID, amount_, l2TxHash);}```k) _bridgeToScroll() - Handles the Bridging Logic to Scroll.
```solfunction _bridgeToScroll(uint256 bridgeFee,bytes memory data) internal returns (bytes memory) {(address recipient_, uint256 amount_) = abi.decode(data,(address, uint256));if (amount_ == type(uint256).max)amount_ = IERC20(lidoStETH).balanceOf(address(this));amount_ = convertToWstEth(amount_);IERC20(lidoWstEth).safeIncreaseAllowance(address(scrollGateway),amount_);scrollGateway.depositERC20{value: bridgeFee}(lidoWstEth,recipient_,amount_,180000);return abi.encode(SCROLL_CHAIN_ID, amount_);}```l) convertToWstEth() - Converts StEth to WstEth.
```solfunction convertToWstEth(uint256 amount) internal returns (uint256) {IERC20(lidoStETH).safeIncreaseAllowance(lidoWstEth, amount);uint256 wstEthBalBefore = IERC20(lidoWstEth).balanceOf(address(this));IWstEth(lidoWstEth).wrap(amount);amount = IERC20(lidoWstEth).balanceOf(address(this)) - wstEthBalBefore;if (amount == 0) revert(Errors.INVALID_AMOUNT);return amount;}```m) getBridgeFee() - Calculates the Bridge Fee for Different Chains.
```solfunction getBridgeFee(uint256 chainId,bytes memory data) internal view returns (uint256) {if (chainId == ARBITRUM_CHAIN_ID) {(,,uint256 maxGas_,uint256 gasPriceBid_,uint256 maxSubmissionCost_) = abi.decode(data, (address, uint256, uint256, uint256, uint256));return maxSubmissionCost_ + maxGas_ * gasPriceBid_;} else if (chainId == ZKSYNC_CHAIN_ID) {(, , , uint256 _l2TxGasLimit) = abi.decode(data,(address, address, uint256, uint256));returnzkSyncBridge.l2TransactionBaseCost(tx.gasprice,_l2TxGasLimit,800);} else if (chainId == SCROLL_CHAIN_ID) {return scrollMessageQueue.estimateCrossDomainMessageFee(180000);} else return 0;}```n) Fallback Function - The fallback function to Receive ETH.
```solreceive() external payable {}``` -
Usage - To use the
DexSpanAdapter
, Deploy the Contract with the required Parameters --
Deploy the Contract - Deploy the LidoStakeEth contract on the Ethereum network by providing the required constructor arguments.
-
Execute Staking and Bridging - Use the Execute function to Stake ETH and Optionally Bridge the resulting StEth to another Chain. The Execute function takes a Data Parameter that includes the ecipient address, amount, Bridge Chain ID, and Bridge-Specific Data.
function execute(bytes calldata data) external payable override returns (address[] memory tokens); -
Bridge Functions - The contract includes functions for bridging to different chains. Each chain-specific bridging function is called internally by the _bridge function based on the provided chain ID.
-
Conclusion
The LidoStakeEth
Contract Provides a Comprehensive Solution for Staking ETH on Lido and Bridging the Resulting StEth to various Layer 2 and Sidechain Networks. By Understanding the Structure and functions of this Contract, Developers can Integrate Lido Staking and Bridging Capabilities into their Decentralized Applications.