文件 1 的 8:IBridgeSwapper.sol
pragma solidity ^0.8.3;
interface IBridgeSwapper {
event Swapped(address _inputToken, uint256 _amountIn, address _outputToken, uint256 _amountOut);
function exchange(uint256 _indexIn, uint256 _indexOut, uint256 _amountIn, uint256 _minAmountOut) external returns (uint256);
}
文件 2 的 8:ICurvePool.sol
pragma solidity ^0.8.3;
interface ICurvePool {
function coins(uint256 _i) external view returns (address);
function lp_token() external view returns (address);
function get_virtual_price() external view returns (uint256);
function get_dy(int128 _i, int128 _j, uint256 _dx) external view returns (uint256);
function exchange(int128 _i, int128 _j, uint256 _dx, uint256 _minDy) external payable returns (uint256);
function add_liquidity(uint256[2] calldata _amounts, uint256 _minMintAmount) external payable returns (uint256);
function remove_liquidity_one_coin(uint256 _amount, int128 _i, uint256 _minAmount) external payable returns (uint256);
function calc_token_amount(uint256[2] calldata _amounts, bool _isDeposit) external view returns (uint256);
function calc_withdraw_one_coin(uint256 _amount, int128 _i) external view returns (uint256);
}
文件 3 的 8:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 4 的 8:ILido.sol
pragma solidity ^0.8.3;
interface ILido {
function submit(address _referral) external payable returns (uint256);
}
文件 5 的 8:IWstETH.sol
pragma solidity ^0.8.3;
interface IWstETH {
function stETH() external returns (address);
function wrap(uint256 _stETHAmount) external returns (uint256);
function unwrap(uint256 _wstETHAmount) external returns (uint256);
}
文件 6 的 8:IZkSync.sol
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IZkSync {
function getPendingBalance(address _address, address _token) external view returns (uint128);
function withdrawPendingBalance(address payable _owner, address _token, uint128 _amount) external;
function depositETH(address _zkSyncAddress) external payable;
function depositERC20(IERC20 _token, uint104 _amount, address _zkSyncAddress) external;
}
文件 7 的 8:LidoBridgeSwapper.sol
pragma solidity ^0.8.3;
import "./ZkSyncBridgeSwapper.sol";
import "./interfaces/IZkSync.sol";
import "./interfaces/IWstETH.sol";
import "./interfaces/ILido.sol";
import "./interfaces/ICurvePool.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract LidoBridgeSwapper is ZkSyncBridgeSwapper {
address public immutable stEth;
address public immutable wStEth;
address public immutable stEthPool;
address public immutable lidoReferral;
constructor(
address _zkSync,
address _l2Account,
address _wStEth,
address _stEthPool,
address _lidoReferral
)
ZkSyncBridgeSwapper(_zkSync, _l2Account)
{
wStEth = _wStEth;
address _stEth = IWstETH(_wStEth).stETH();
require(_stEth == ICurvePool(_stEthPool).coins(1), "stEth mismatch");
stEth = _stEth;
stEthPool = _stEthPool;
lidoReferral = _lidoReferral;
}
function exchange(
uint256 _indexIn,
uint256 _indexOut,
uint256 _amountIn,
uint256 _minAmountOut
)
onlyOwner
external
override
returns (uint256 amountOut)
{
require(_indexIn + _indexOut == 1, "invalid indexes");
if (_indexIn == 0) {
transferFromZkSync(ETH_TOKEN);
amountOut = swapEthForWstEth(_amountIn);
require(amountOut >= _minAmountOut, "slippage");
transferToZkSync(wStEth, amountOut);
emit Swapped(ETH_TOKEN, _amountIn, wStEth, amountOut);
} else {
transferFromZkSync(wStEth);
amountOut = swapWstEthForEth(_amountIn);
require(amountOut >= _minAmountOut, "slippage");
transferToZkSync(ETH_TOKEN, amountOut);
emit Swapped(wStEth, _amountIn, ETH_TOKEN, amountOut);
}
}
function swapEthForWstEth(uint256 _amountIn) internal returns (uint256) {
uint256 dy = ICurvePool(stEthPool).get_dy(0, 1, _amountIn);
uint256 stEthAmount;
if (dy > _amountIn) {
stEthAmount = ICurvePool(stEthPool).exchange{value: _amountIn}(0, 1, _amountIn, 1);
} else {
ILido(stEth).submit{value: _amountIn}(lidoReferral);
stEthAmount = _amountIn;
}
IERC20(stEth).approve(wStEth, stEthAmount);
return IWstETH(wStEth).wrap(stEthAmount);
}
function swapWstEthForEth(uint256 _amountIn) internal returns (uint256) {
uint256 unwrapped = IWstETH(wStEth).unwrap(_amountIn);
bool success = IERC20(stEth).approve(stEthPool, unwrapped);
require(success, "approve failed");
return ICurvePool(stEthPool).exchange(1, 0, unwrapped, 1);
}
}
文件 8 的 8:ZkSyncBridgeSwapper.sol
pragma solidity ^0.8.3;
import "./interfaces/IZkSync.sol";
import "./interfaces/IBridgeSwapper.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
abstract contract ZkSyncBridgeSwapper is IBridgeSwapper {
address public owner;
address public immutable zkSync;
address public immutable l2Account;
address constant internal ETH_TOKEN = address(0);
event OwnerChanged(address _owner, address _newOwner);
event SlippageChanged(uint256 _slippagePercent);
modifier onlyOwner {
require(msg.sender == owner, "unauthorised");
_;
}
constructor(address _zkSync, address _l2Account) {
zkSync = _zkSync;
l2Account = _l2Account;
owner = msg.sender;
}
function changeOwner(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "invalid input");
owner = _newOwner;
emit OwnerChanged(owner, _newOwner);
}
function transferFromZkSync(address _token) internal {
uint128 pendingBalance = IZkSync(zkSync).getPendingBalance(address(this), _token);
if (pendingBalance > 0) {
IZkSync(zkSync).withdrawPendingBalance(payable(address(this)), _token, pendingBalance);
}
}
function transferToZkSync(address _outputToken, uint256 _amountOut) internal {
if (_outputToken == ETH_TOKEN) {
IZkSync(zkSync).depositETH{value: _amountOut}(l2Account);
} else {
IERC20(_outputToken).approve(zkSync, _amountOut);
IZkSync(zkSync).depositERC20(IERC20(_outputToken), toUint104(_amountOut), l2Account);
}
}
function recoverToken(address _recipient, address _token) external onlyOwner returns (uint256 balance) {
bool success;
if (_token == ETH_TOKEN) {
balance = address(this).balance;
(success, ) = _recipient.call{value: balance}("");
} else {
balance = IERC20(_token).balanceOf(address(this));
success = IERC20(_token).transfer(_recipient, balance);
}
require(success, "failed to recover");
}
receive() external payable {
}
function toUint104(uint256 value) internal pure returns (uint104) {
require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
return uint104(value);
}
}
{
"compilationTarget": {
"contracts/LidoBridgeSwapper.sol": "LidoBridgeSwapper"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_zkSync","type":"address"},{"internalType":"address","name":"_l2Account","type":"address"},{"internalType":"address","name":"_wStEth","type":"address"},{"internalType":"address","name":"_stEthPool","type":"address"},{"internalType":"address","name":"_lidoReferral","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_owner","type":"address"},{"indexed":false,"internalType":"address","name":"_newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_slippagePercent","type":"uint256"}],"name":"SlippageChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_inputToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amountIn","type":"uint256"},{"indexed":false,"internalType":"address","name":"_outputToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"_amountOut","type":"uint256"}],"name":"Swapped","type":"event"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"changeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_indexIn","type":"uint256"},{"internalType":"uint256","name":"_indexOut","type":"uint256"},{"internalType":"uint256","name":"_amountIn","type":"uint256"},{"internalType":"uint256","name":"_minAmountOut","type":"uint256"}],"name":"exchange","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"l2Account","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lidoReferral","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"address","name":"_token","type":"address"}],"name":"recoverToken","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stEth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stEthPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wStEth","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"zkSync","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]