Diff
checker
텍스트
텍스트
이미지
문서
Excel
폴더
Legal
Enterprise
데스크톱
요금제
로그인
데스크톱 앱 다운로드
텍스트 비교
두 텍스트 파일의 차이점을 찾아보세요
도구
기록
표시
레이아웃
나란히 보기
합쳐 보기
실시간 편집
공백 변경 숨기기
변경 없는 행 숨기기
줄바꿈 비활성화
비교 단위
스마트
구문
없음
모양 변경
처리
제외
텍스트 변환
첫 변경으로
수정
Diffchecker Desktop
가장 안전하게 Diffchecker를 사용하는 방법. 데스크톱 앱을 사용하면 비교 데이터가 외부로 전송되지 않습니다!
데스크톱 앱 받기
Base rsETH pool diff (April 2026)
생성일
7개월 전
비교 결과 만료 없음
초기화
내보내기
공유
설명
183 삭제
행
총
삭제
글자
총
삭제
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
676 행
복사
208 추가
행
총
추가
글자
총
추가
이 기능을 계속 사용하려면 업그레이드해 주세요
Diff
checker
Pro
요금제 보기
687 행
복사
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1
복사
복사됨
복사
복사됨
pragma solidity 0.8.
21
;
pragma solidity 0.8.
34
;
import {
import {
복사
복사됨
복사
복사됨
ERC20Upgradeable,
IERC20Upgradeable
ERC20Upgradeable,
IERC20Upgradeable
} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
복사
복사됨
복사
복사됨
import {
ReentrancyGuardUpgradeable
} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {
ReentrancyGuardUpgradeable
} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { UtilLib } from "contracts/utils/UtilLib.sol";
import { UtilLib } from "contracts/utils/UtilLib.sol";
import {
import {
IStargatePoolNative,
IStargatePoolNative,
SendParam,
SendParam,
MessagingFee,
MessagingFee,
OFTReceipt,
OFTReceipt,
MessagingReceipt,
MessagingReceipt,
TxReceipt
TxReceipt
} from "contracts/external/layerzero/interfaces/IStargatePoolNative.sol";
} from "contracts/external/layerzero/interfaces/IStargatePoolNative.sol";
복사
복사됨
복사
복사됨
import { IL2Messenger } from "contracts/interfaces/L2/IL2Messenger.sol";
import { IL2TokenBridge } from "contracts/interfaces/L2/IL2TokenBridge.sol";
import { IL2TokenBridge } from "contracts/interfaces/L2/IL2TokenBridge.sol";
interface IOracle {
interface IOracle {
function getRate() external view returns (uint256);
function getRate() external view returns (uint256);
}
}
interface IERC20WrsETH is IERC20Upgradeable {
interface IERC20WrsETH is IERC20Upgradeable {
function mint(address to, uint256 amount) external;
function mint(address to, uint256 amount) external;
}
}
복사
복사됨
복사
복사됨
interface IRsETHTokenWrapper {
function allowedTokens(address asset) external view returns (bool);
function maxAmountToDepositBridgerAsset(address asset) external view returns (uint256);
}
/// @title RSETHPoolV3ExternalBridge
/// @title RSETHPoolV3ExternalBridge
/// @notice This contract is the pool for swapping ETH for rsETH. It uses external bridges (e.g. LayerZero/Stargate) for
/// @notice This contract is the pool for swapping ETH for rsETH. It uses external bridges (e.g. LayerZero/Stargate) for
/// bridging ETH from L2s to L1 and native bridging for LSTs (e.g. wstETH).
/// bridging ETH from L2s to L1 and native bridging for LSTs (e.g. wstETH).
contract RSETHPoolV3ExternalBridge is ERC20Upgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
contract RSETHPoolV3ExternalBridge is ERC20Upgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable {
using SafeERC20 for IERC20;
using SafeERC20 for IERC20;
IERC20WrsETH public wrsETH;
IERC20WrsETH public wrsETH;
uint256 public feeBps; // Basis points for fees
uint256 public feeBps; // Basis points for fees
uint256 public feeEarnedInETH;
uint256 public feeEarnedInETH;
address public rsETHOracle;
address public rsETHOracle;
bytes32 public constant BRIDGER_ROLE = keccak256("BRIDGER_ROLE");
bytes32 public constant BRIDGER_ROLE = keccak256("BRIDGER_ROLE");
bytes32 public constant TIMELOCK_ROLE = keccak256("TIMELOCK_ROLE");
bytes32 public constant TIMELOCK_ROLE = keccak256("TIMELOCK_ROLE");
/// @notice The corresponding L1Vault contract for the L2 chain
/// @notice The corresponding L1Vault contract for the L2 chain
address public l1VaultETHForL2Chain;
address public l1VaultETHForL2Chain;
/// @notice The StargatePool used for L2 --> L1 bridging
/// @notice The StargatePool used for L2 --> L1 bridging
IStargatePoolNative public stargatePool;
IStargatePoolNative public stargatePool;
/// @notice The LayerZero ID for the ETH mainnet
/// @notice The LayerZero ID for the ETH mainnet
uint32 public dstLzChainId;
uint32 public dstLzChainId;
/// @notice The latest transaction receipt info from the StargatePoolNative
/// @notice The latest transaction receipt info from the StargatePoolNative
TxReceipt public latestTxReceipt;
TxReceipt public latestTxReceipt;
/// @notice New variable added for pausable functionality
/// @notice New variable added for pausable functionality
bool public paused;
bool public paused;
/// @notice THe daily minting limit for rsETH
/// @notice THe daily minting limit for rsETH
uint256 public dailyMintLimit;
uint256 public dailyMintLimit;
/// @notice The amount of rsETH that was minted today
/// @notice The amount of rsETH that was minted today
uint256 public dailyMintAmount;
uint256 public dailyMintAmount;
/// @notice The last day that rsETH was minted
/// @notice The last day that rsETH was minted
uint256 public lastMintDay;
uint256 public lastMintDay;
/// @notice The start timestamp for the daily minting limit
/// @notice The start timestamp for the daily minting limit
uint256 public startTimestamp;
uint256 public startTimestamp;
/// @notice ETH identifier address
/// @notice ETH identifier address
address public constant ETH_IDENTIFIER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public constant ETH_IDENTIFIER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice The mapping of token addresses to the fee earned in that token
/// @notice The mapping of token addresses to the fee earned in that token
mapping(address token => uint256 feeEarned) public feeEarnedInToken;
mapping(address token => uint256 feeEarned) public feeEarnedInToken;
/// @notice The mapping of token addresses to their respective oracle addresses
/// @notice The mapping of token addresses to their respective oracle addresses
mapping(address token => address oracle) public supportedTokenOracle;
mapping(address token => address oracle) public supportedTokenOracle;
/// @notice The mapping of token addresses to their respective token bridges
/// @notice The mapping of token addresses to their respective token bridges
mapping(address token => address bridge) public tokenBridge;
mapping(address token => address bridge) public tokenBridge;
/// @notice An array of supported token addresses
/// @notice An array of supported token addresses
address[] public supportedTokenList;
address[] public supportedTokenList;
복사
복사됨
복사
복사됨
/// @notice The pauser role identifier
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @notice The address of the L2 bridge contract
address public l2Bridge;
/// @notice The address of the L2 messenger contract
address public messenger;
/// @notice The operator role identifier
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
modifier whenNotPaused() {
modifier whenNotPaused() {
if (paused) revert ContractPaused();
if (paused) revert ContractPaused();
_;
_;
}
}
modifier whenPaused() {
modifier whenPaused() {
if (!paused) revert ContractNotPaused();
if (!paused) revert ContractNotPaused();
_;
_;
}
}
modifier onlySupportedToken(address token) {
modifier onlySupportedToken(address token) {
if (supportedTokenOracle[token] == address(0)) revert UnsupportedToken();
if (supportedTokenOracle[token] == address(0)) revert UnsupportedToken();
_;
_;
}
}
저장된 비교 결과
원본
파일 열기
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.21; import { ERC20Upgradeable, IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { UtilLib } from "contracts/utils/UtilLib.sol"; import { IStargatePoolNative, SendParam, MessagingFee, OFTReceipt, MessagingReceipt, TxReceipt } from "contracts/external/layerzero/interfaces/IStargatePoolNative.sol"; import { IL2TokenBridge } from "contracts/interfaces/L2/IL2TokenBridge.sol"; interface IOracle { function getRate() external view returns (uint256); } interface IERC20WrsETH is IERC20Upgradeable { function mint(address to, uint256 amount) external; } /// @title RSETHPoolV3ExternalBridge /// @notice This contract is the pool for swapping ETH for rsETH. It uses external bridges (e.g. LayerZero/Stargate) for /// bridging ETH from L2s to L1 and native bridging for LSTs (e.g. wstETH). contract RSETHPoolV3ExternalBridge is ERC20Upgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; IERC20WrsETH public wrsETH; uint256 public feeBps; // Basis points for fees uint256 public feeEarnedInETH; address public rsETHOracle; bytes32 public constant BRIDGER_ROLE = keccak256("BRIDGER_ROLE"); bytes32 public constant TIMELOCK_ROLE = keccak256("TIMELOCK_ROLE"); /// @notice The corresponding L1Vault contract for the L2 chain address public l1VaultETHForL2Chain; /// @notice The StargatePool used for L2 --> L1 bridging IStargatePoolNative public stargatePool; /// @notice The LayerZero ID for the ETH mainnet uint32 public dstLzChainId; /// @notice The latest transaction receipt info from the StargatePoolNative TxReceipt public latestTxReceipt; /// @notice New variable added for pausable functionality bool public paused; /// @notice THe daily minting limit for rsETH uint256 public dailyMintLimit; /// @notice The amount of rsETH that was minted today uint256 public dailyMintAmount; /// @notice The last day that rsETH was minted uint256 public lastMintDay; /// @notice The start timestamp for the daily minting limit uint256 public startTimestamp; /// @notice ETH identifier address address public constant ETH_IDENTIFIER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice The mapping of token addresses to the fee earned in that token mapping(address token => uint256 feeEarned) public feeEarnedInToken; /// @notice The mapping of token addresses to their respective oracle addresses mapping(address token => address oracle) public supportedTokenOracle; /// @notice The mapping of token addresses to their respective token bridges mapping(address token => address bridge) public tokenBridge; /// @notice An array of supported token addresses address[] public supportedTokenList; modifier whenNotPaused() { if (paused) revert ContractPaused(); _; } modifier whenPaused() { if (!paused) revert ContractNotPaused(); _; } modifier onlySupportedToken(address token) { if (supportedTokenOracle[token] == address(0)) revert UnsupportedToken(); _; } /// @dev Modifier to enforce the daily minting limit /// @param amount The asset amount sent in the deposit /// @param token The token address modifier limitDailyMint(uint256 amount, address token) { if (block.timestamp < startTimestamp) { revert MintBeforeStartTimestamp(); } uint256 rsETHAmount; // Calculate the amount of rsETH that will be minted if (token == ETH_IDENTIFIER) { (rsETHAmount,) = viewSwapRsETHAmountAndFee(amount); } else { (rsETHAmount,) = viewSwapRsETHAmountAndFee(amount, token); } uint256 currentDay = getCurrentDay(); // If the current day is greater than the last mint day, reset the daily mint amount if (currentDay > lastMintDay) { lastMintDay = currentDay; dailyMintAmount = 0; } // Check if the daily mint amount plus the amount to mint is greater than the daily mint limit if (dailyMintAmount + rsETHAmount > dailyMintLimit) { revert DailyMintLimitExceeded(); } dailyMintAmount += rsETHAmount; _; } /// @notice Custom errors error InvalidAmount(); error TransferFailed(); error InsufficientETHBalance(); error InvalidMinAmount(); error InsufficientNativeFee(); error InvalidSlippageTolerance(); error ContractPaused(); error ContractNotPaused(); error DailyMintLimitExceeded(); error InvalidDailyMintLimit(); error MintBeforeStartTimestamp(); error InvalidStartTimestamp(); error DeprecatedFunction(); error InvalidLzChainId(); error UnsupportedToken(); error InvalidOracle(); error AlreadySupportedToken(); error TokenNotFoundError(); error ZeroBridgeAmount(); error MissingBridgeForToken(); error InvalidFeeAmount(); /// @notice Events event SwapOccurred(address indexed user, uint256 rsETHAmount, uint256 fee, string referralId); event SwapOccurred( address indexed user, uint256 rsETHAmount, uint256 fee, string referralId, address indexed depositedToken ); event FeesWithdrawn(uint256 feeEarnedInETH); event FeesWithdrawn(uint256 feeEarnedInETH, address token); event BridgedETHToL1(uint32 lzChainId, address l1Receiver, uint256 amountSent, uint256 amountReceived); event BridgedTokenToL1(address indexed token, address l1Receiver, uint256 amountSent); event FeeBpsSet(uint256 feeBps); event OracleSet(address oracle); event AddSupportedToken(address token, address oracle, address bridge); event RemovedSupportedToken(address token); event L1VaultETHForL2ChainSet(address l1VaultETHForL2Chain); event StargatePoolSet(address stargatePool); event LzChainIdSet(uint32 lzChainId); event Paused(address account); event Unpaused(address account); event DailyMintLimitSet(uint256 dailyMintLimit); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /// @dev Reinitializer function to set the initial supported token /// @param token The token address /// @param oracle The oracle address /// @param bridge The bridge address function reinitialize( address token, address oracle, address bridge ) external reinitializer(5) onlyRole(DEFAULT_ADMIN_ROLE) { _addSupportedToken(token, oracle, bridge); } /// @dev Reinitializer function to set the daily minting limit /// @param _dailyMintLimit The daily minting limit /// @param _startTimestamp The start timestamp for the daily minting limit function reinitialize( uint256 _dailyMintLimit, uint256 _startTimestamp ) public reinitializer(4) onlyRole(DEFAULT_ADMIN_ROLE) { if (_dailyMintLimit == 0) { revert InvalidDailyMintLimit(); } // startTimestamp cannot be in the past if (block.timestamp > _startTimestamp) { revert InvalidStartTimestamp(); } dailyMintLimit = _dailyMintLimit; startTimestamp = _startTimestamp; } /// @dev Reinitialize the contract /// @param _dstLzChainId The LayerZero ID for the ETH mainnet function reinitialize(uint32 _dstLzChainId) public reinitializer(3) onlyRole(DEFAULT_ADMIN_ROLE) { dstLzChainId = _dstLzChainId; } /// @dev Reinitialize the contract /// @param _l1VaultETHForL2Chain The address of the L1VaultETH for the L2 chain /// @param _stargatePool The address of the StargatePool used for L2 --> L1 bridging /// @param _dstLzChainId The LayerZero ID for the ETH mainnet function reinitialize( address _l1VaultETHForL2Chain, address _stargatePool, uint32 _dstLzChainId ) public reinitializer(2) onlyRole(DEFAULT_ADMIN_ROLE) { UtilLib.checkNonZeroAddress(_l1VaultETHForL2Chain); UtilLib.checkNonZeroAddress(_stargatePool); l1VaultETHForL2Chain = _l1VaultETHForL2Chain; stargatePool = IStargatePoolNative(_stargatePool); dstLzChainId = _dstLzChainId; } /// @dev Initialize the contract /// @param admin The admin address /// @param bridger The bridger address /// @param _wrsETH The rsETH token address /// @param _feeBps The fee basis points /// @param _rsETHOracle The rsETHOracle address function initialize( address admin, address bridger, address _wrsETH, uint256 _feeBps, address _rsETHOracle ) public initializer { UtilLib.checkNonZeroAddress(_wrsETH); UtilLib.checkNonZeroAddress(_rsETHOracle); __ERC20_init("rsETH", "rsETH"); __AccessControl_init(); __ReentrancyGuard_init(); _grantRole(DEFAULT_ADMIN_ROLE, admin); _setupRole(BRIDGER_ROLE, admin); _setupRole(BRIDGER_ROLE, bridger); wrsETH = IERC20WrsETH(_wrsETH); feeBps = _feeBps; rsETHOracle = _rsETHOracle; } /// @dev Gets the rate from the rsETHOracle function getRate() public view returns (uint256) { return IOracle(rsETHOracle).getRate(); } /// @dev Returns the list of supported tokens function getSupportedTokens() external view returns (address[] memory) { return supportedTokenList; } /// @dev Swaps ETH for rsETH /// @param referralId The referral id function deposit(string memory referralId) external payable whenNotPaused nonReentrant limitDailyMint(msg.value, ETH_IDENTIFIER) { uint256 amount = msg.value; if (amount == 0) revert InvalidAmount(); (uint256 rsETHAmount, uint256 fee) = viewSwapRsETHAmountAndFee(amount); feeEarnedInETH += fee; wrsETH.mint(msg.sender, rsETHAmount); emit SwapOccurred(msg.sender, rsETHAmount, fee, referralId); } /// @dev Swaps supported token for rsETH /// @param token The token address /// @param amount The amount of token /// @param referralId The referral id function deposit( address token, uint256 amount, string memory referralId ) external whenNotPaused nonReentrant onlySupportedToken(token) limitDailyMint(amount, token) { if (amount == 0) revert InvalidAmount(); IERC20(token).safeTransferFrom(msg.sender, address(this), amount); (uint256 rsETHAmount, uint256 fee) = viewSwapRsETHAmountAndFee(amount, token); feeEarnedInToken[token] += fee; wrsETH.mint(msg.sender, rsETHAmount); emit SwapOccurred(msg.sender, rsETHAmount, fee, referralId, token); } /// @dev view function to get the rsETH amount for a given amount of ETH /// @param amount The amount of ETH /// @return rsETHAmount The amount of rsETH that will be received /// @return fee The fee that will be charged function viewSwapRsETHAmountAndFee(uint256 amount) public view returns (uint256 rsETHAmount, uint256 fee) { fee = amount * feeBps / 10_000; uint256 amountAfterFee = amount - fee; // rate of rsETH in ETH uint256 rsETHToETHrate = getRate(); // Calculate the final rsETH amount rsETHAmount = amountAfterFee * 1e18 / rsETHToETHrate; } /// @dev view function to get the rsETH amount for a given amount of token /// @param amount The amount of token /// @return rsETHAmount The amount of rsETH that will be received /// @return fee The fee that will be charged function viewSwapRsETHAmountAndFee( uint256 amount, address token ) public view onlySupportedToken(token) returns (uint256 rsETHAmount, uint256 fee) { fee = amount * feeBps / 10_000; uint256 amountAfterFee = amount - fee; // rate of rsETH in ETH uint256 rsETHToETHrate = getRate(); // rate of token in ETH uint256 tokenToETHRate = IOracle(supportedTokenOracle[token]).getRate(); // Calculate the final rsETH amount rsETHAmount = amountAfterFee * tokenToETHRate / rsETHToETHrate; } /** * @dev Quote the native fee for sending RsETH to L2 * @param amount The amount of RsETH to send * @param minAmount The minimum amount of RsETH to receive on L2 * @return The fee to be paid in native currency */ function getNativeFee(uint256 amount, uint256 minAmount) external view returns (uint256) { if (minAmount > amount || minAmount == 0) { revert InvalidMinAmount(); } SendParam memory sendParam = SendParam({ dstEid: dstLzChainId, to: getReceiver(), amountLD: amount, minAmountLD: minAmount, extraOptions: bytes(""), composeMsg: bytes(""), oftCmd: bytes("") }); MessagingFee memory fee = stargatePool.quoteSend(sendParam, false); return fee.nativeFee; } /** * @dev Get the receiver address in the bytes32 format * @return The receiver address in the bytes32 format */ function getReceiver() public view returns (bytes32) { return bytes32(uint256(uint160(l1VaultETHForL2Chain))); } /** * @dev Get the ETH balance minus the fees * @return The ETH balance minus the fees */ function getETHBalanceMinusFees() public view returns (uint256) { return address(this).balance - feeEarnedInETH; } /** * @dev Get the token balance minus the fees * @param token The token address * @return The token balance minus the fees */ function getTokenBalanceMinusFees(address token) public view returns (uint256) { return IERC20(token).balanceOf(address(this)) - feeEarnedInToken[token]; } /** * @dev Get the minimum amount after slippage * @param amount The amount * @param slippageTolerance The slippage tolerance * @return The minimum amount after slippage */ function getMinAmount(uint256 amount, uint256 slippageTolerance) public pure returns (uint256) { if (slippageTolerance > 100) revert InvalidSlippageTolerance(); return amount - (amount * slippageTolerance / 10_000); } /// @notice Gets the current day relative to the start timestamp /// @return uint256 The current day relative to the start timestamp function getCurrentDay() public view returns (uint256) { return (block.timestamp - startTimestamp) / 1 days; } /// @notice Gets the remaining daily minting limit /// @return uint256 The remaining daily minting limit function remainingDailyMintLimit() external view returns (uint256) { // If we're on a new day but no mint has occurred yet, treat dailyMintAmount as 0 uint256 effectiveDailyMintAmount = (getCurrentDay() > lastMintDay) ? 0 : dailyMintAmount; return dailyMintLimit - effectiveDailyMintAmount; } /// @notice Gets the next daily mint limit reset timestamp /// @return uint256 The next daily mint limit reset timestamp function getNextDailyLimitResetTimestamp() external view returns (uint256) { return startTimestamp + (getCurrentDay() + 1) * 1 days; } /*////////////////////////////////////////////////////////////// ACCESS RESTRICTED FUNCTIONS //////////////////////////////////////////////////////////////*/ /// @dev Withdraws fees earned by the pool in ETH function withdrawFees(address receiver) external onlyRole(BRIDGER_ROLE) { // withdraw fees in ETH uint256 amountToSendInETH = feeEarnedInETH; feeEarnedInETH = 0; (bool success,) = payable(receiver).call{ value: amountToSendInETH }(""); if (!success) revert TransferFailed(); emit FeesWithdrawn(amountToSendInETH); } /// @dev Withdraws fees earned by the pool in a specific token function withdrawFees(address receiver, address token) external onlySupportedToken(token) onlyRole(BRIDGER_ROLE) { // withdraw fees in token uint256 amountToSendInToken = feeEarnedInToken[token]; feeEarnedInToken[token] = 0; IERC20(token).safeTransfer(receiver, amountToSendInToken); emit FeesWithdrawn(amountToSendInToken, token); } /// @dev Legacy function - Withdraws assets from the contract for bridging function moveAssetsForBridging() external view onlyRole(BRIDGER_ROLE) { revert DeprecatedFunction(); } /// @dev Withdraws ETH from the L2 to L1 using LayerZero /// @param amount The amount of ETH to bridge /// @param minAmount The minimum amount of ETH to receive on L1 /// @param nativeFee The native fee to pay for the bridge function bridgeAssets( uint256 amount, uint256 minAmount, uint256 nativeFee ) external payable nonReentrant onlyRole(BRIDGER_ROLE) { if (getETHBalanceMinusFees() < amount) { revert InsufficientETHBalance(); } if (minAmount > amount || minAmount == 0) { revert InvalidMinAmount(); } if (msg.value < nativeFee) { revert InsufficientNativeFee(); } SendParam memory sendParam = SendParam({ dstEid: dstLzChainId, to: getReceiver(), amountLD: amount, minAmountLD: minAmount, extraOptions: bytes(""), composeMsg: bytes(""), oftCmd: bytes("") }); MessagingFee memory fee = MessagingFee({ nativeFee: nativeFee, lzTokenFee: 0 }); (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = stargatePool.send{ value: nativeFee + amount }(sendParam, fee, msg.sender); latestTxReceipt = TxReceipt({ guid: msgReceipt.guid, amountReceivedLD: oftReceipt.amountReceivedLD }); emit BridgedETHToL1(dstLzChainId, l1VaultETHForL2Chain, oftReceipt.amountSentLD, oftReceipt.amountReceivedLD); } /// @dev Bridges tokens to L1 using their corresponding token bridge /// @param token The address of the token to bridge function bridgeTokens(address token) external payable nonReentrant onlySupportedToken(token) onlyRole(BRIDGER_ROLE) { if (tokenBridge[token] == address(0)) { revert MissingBridgeForToken(); } uint256 balance = IERC20(token).balanceOf(address(this)); if (balance == 0) { revert ZeroBridgeAmount(); } // Approve the required amount to the bridge IERC20(token).safeIncreaseAllowance(tokenBridge[token], balance); // Call the bridge contract to transfer the tokens (msg.value is included in case we need to pay for additional // bridging fees) IL2TokenBridge(tokenBridge[token]).bridgeTokenToL1{ value: msg.value }(l1VaultETHForL2Chain, balance); emit BridgedTokenToL1(token, l1VaultETHForL2Chain, balance); } /// @dev Sets the fee basis points /// @param _feeBps The fee basis points function setFeeBps(uint256 _feeBps) external onlyRole(TIMELOCK_ROLE) { if (_feeBps > 10_000) revert InvalidFeeAmount(); feeBps = _feeBps; emit FeeBpsSet(_feeBps); } /// @dev Sets the rsETHOracle address /// @param _rsETHOracle The rsETHOracle address function setRSETHOracle(address _rsETHOracle) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_rsETHOracle); rsETHOracle = _rsETHOracle; emit OracleSet(_rsETHOracle); } /// @dev Adds a supported token /// @param token The token address /// @param oracle The oracle address /// @param bridge The bridge address function addSupportedToken(address token, address oracle, address bridge) external onlyRole(TIMELOCK_ROLE) { _addSupportedToken(token, oracle, bridge); } /// @dev Removes a supported token /// @param token The token address /// @param tokenIndex The index of the token in the supportedTokenList function removeSupportedToken(address token, uint256 tokenIndex) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(token); if (supportedTokenList[tokenIndex] != token) { revert TokenNotFoundError(); } delete supportedTokenOracle[token]; delete tokenBridge[token]; supportedTokenList[tokenIndex] = supportedTokenList[supportedTokenList.length - 1]; supportedTokenList.pop(); emit RemovedSupportedToken(token); } /// @dev Sets the new L1VaultETH for the L2 chain /// @param _l1VaultETHForL2Chain The new L1VaultETH for the L2 chain function setL1VaultETHForL2Chain(address _l1VaultETHForL2Chain) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_l1VaultETHForL2Chain); l1VaultETHForL2Chain = _l1VaultETHForL2Chain; emit L1VaultETHForL2ChainSet(_l1VaultETHForL2Chain); } /// @dev Sets the new stargatePool address /// @param _stargatePool The new stargatePool address function setStargatePool(address _stargatePool) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_stargatePool); stargatePool = IStargatePoolNative(_stargatePool); emit StargatePoolSet(_stargatePool); } /// @dev Sets the destination LayerZero chain ID /// @param _dstLzChainId The destination LayerZero chain ID function setDstLzChainId(uint32 _dstLzChainId) external onlyRole(TIMELOCK_ROLE) { if (_dstLzChainId == 0) { revert InvalidLzChainId(); } dstLzChainId = _dstLzChainId; emit LzChainIdSet(_dstLzChainId); } /// @dev Pauses the pausable methods in the contract function pause() external onlyRole(DEFAULT_ADMIN_ROLE) { paused = true; emit Paused(msg.sender); } /// @dev Unpauses the pausable methods in the contract function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) { paused = false; emit Unpaused(msg.sender); } /// @dev Sets the daily minting limit /// @param _dailyMintLimit The new daily minting limit function setDailyMintLimit(uint256 _dailyMintLimit) external onlyRole(DEFAULT_ADMIN_ROLE) { if (_dailyMintLimit == 0) { revert InvalidDailyMintLimit(); } dailyMintLimit = _dailyMintLimit; emit DailyMintLimitSet(_dailyMintLimit); } /// @dev Internal function to add a supported token function _addSupportedToken(address token, address oracle, address bridge) internal { UtilLib.checkNonZeroAddress(token); UtilLib.checkNonZeroAddress(oracle); UtilLib.checkNonZeroAddress(bridge); if (supportedTokenOracle[token] != address(0)) { revert AlreadySupportedToken(); } if (tokenBridge[token] != address(0)) { revert AlreadySupportedToken(); } if (IOracle(oracle).getRate() == 0) { revert InvalidOracle(); } supportedTokenList.push(token); supportedTokenOracle[token] = oracle; tokenBridge[token] = bridge; emit AddSupportedToken(token, oracle, bridge); } }
수정본
파일 열기
// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.34; import { ERC20Upgradeable, IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { UtilLib } from "contracts/utils/UtilLib.sol"; import { IStargatePoolNative, SendParam, MessagingFee, OFTReceipt, MessagingReceipt, TxReceipt } from "contracts/external/layerzero/interfaces/IStargatePoolNative.sol"; import { IL2Messenger } from "contracts/interfaces/L2/IL2Messenger.sol"; import { IL2TokenBridge } from "contracts/interfaces/L2/IL2TokenBridge.sol"; interface IOracle { function getRate() external view returns (uint256); } interface IERC20WrsETH is IERC20Upgradeable { function mint(address to, uint256 amount) external; } interface IRsETHTokenWrapper { function allowedTokens(address asset) external view returns (bool); function maxAmountToDepositBridgerAsset(address asset) external view returns (uint256); } /// @title RSETHPoolV3ExternalBridge /// @notice This contract is the pool for swapping ETH for rsETH. It uses external bridges (e.g. LayerZero/Stargate) for /// bridging ETH from L2s to L1 and native bridging for LSTs (e.g. wstETH). contract RSETHPoolV3ExternalBridge is ERC20Upgradeable, AccessControlUpgradeable, ReentrancyGuardUpgradeable { using SafeERC20 for IERC20; IERC20WrsETH public wrsETH; uint256 public feeBps; // Basis points for fees uint256 public feeEarnedInETH; address public rsETHOracle; bytes32 public constant BRIDGER_ROLE = keccak256("BRIDGER_ROLE"); bytes32 public constant TIMELOCK_ROLE = keccak256("TIMELOCK_ROLE"); /// @notice The corresponding L1Vault contract for the L2 chain address public l1VaultETHForL2Chain; /// @notice The StargatePool used for L2 --> L1 bridging IStargatePoolNative public stargatePool; /// @notice The LayerZero ID for the ETH mainnet uint32 public dstLzChainId; /// @notice The latest transaction receipt info from the StargatePoolNative TxReceipt public latestTxReceipt; /// @notice New variable added for pausable functionality bool public paused; /// @notice THe daily minting limit for rsETH uint256 public dailyMintLimit; /// @notice The amount of rsETH that was minted today uint256 public dailyMintAmount; /// @notice The last day that rsETH was minted uint256 public lastMintDay; /// @notice The start timestamp for the daily minting limit uint256 public startTimestamp; /// @notice ETH identifier address address public constant ETH_IDENTIFIER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice The mapping of token addresses to the fee earned in that token mapping(address token => uint256 feeEarned) public feeEarnedInToken; /// @notice The mapping of token addresses to their respective oracle addresses mapping(address token => address oracle) public supportedTokenOracle; /// @notice The mapping of token addresses to their respective token bridges mapping(address token => address bridge) public tokenBridge; /// @notice An array of supported token addresses address[] public supportedTokenList; /// @notice The pauser role identifier bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); /// @notice The address of the L2 bridge contract address public l2Bridge; /// @notice The address of the L2 messenger contract address public messenger; /// @notice The operator role identifier bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); modifier whenNotPaused() { if (paused) revert ContractPaused(); _; } modifier whenPaused() { if (!paused) revert ContractNotPaused(); _; } modifier onlySupportedToken(address token) { if (supportedTokenOracle[token] == address(0)) revert UnsupportedToken(); _; } modifier onlySupportedTokenOrEth(address token) { if (token != ETH_IDENTIFIER && supportedTokenOracle[token] == address(0)) { revert UnsupportedToken(); } _; } /// @dev Modifier to enforce the daily minting limit /// @param amount The asset amount sent in the deposit /// @param token The token address modifier limitDailyMint(uint256 amount, address token) { if (block.timestamp < startTimestamp) { revert MintBeforeStartTimestamp(); } uint256 rsETHAmount; // Calculate the amount of rsETH that will be minted if (token == ETH_IDENTIFIER) { (rsETHAmount,) = viewSwapRsETHAmountAndFee(amount); } else { (rsETHAmount,) = viewSwapRsETHAmountAndFee(amount, token); } uint256 currentDay = getCurrentDay(); // If the current day is greater than the last mint day, reset the daily mint amount if (currentDay > lastMintDay) { lastMintDay = currentDay; dailyMintAmount = 0; } // Check if the daily mint amount plus the amount to mint is greater than the daily mint limit if (dailyMintAmount + rsETHAmount > dailyMintLimit) { revert DailyMintLimitExceeded(); } dailyMintAmount += rsETHAmount; _; } /// @notice Custom errors error InvalidAmount(); error TransferFailed(); error InsufficientETHBalance(); error InvalidMinAmount(); error IncorrectNativeFee(); error InvalidSlippageTolerance(); error ContractPaused(); error ContractNotPaused(); error DailyMintLimitExceeded(); error InvalidDailyMintLimit(); error MintBeforeStartTimestamp(); error InvalidStartTimestamp(); error DeprecatedFunction(); error InvalidLzChainId(); error UnsupportedToken(); error UnsupportedOracle(); error AlreadySupportedToken(); error TokenNotFoundError(); error TokenBalanceNotZero(); error ZeroBridgeAmount(); error MissingBridgeForToken(); error InvalidFeeAmount(); error TokenNotAllowedInWrapper(); error ExceedsMaxAmountToDepositInWrapper(); error InsufficientETHBalanceForReverseSwap(); error InsufficientAssetBalanceForReverseSwap(); /// @notice Events event SwapOccurred(address indexed user, uint256 rsETHAmount, uint256 fee, string referralId); event SwapOccurred( address indexed user, uint256 rsETHAmount, uint256 fee, string referralId, address indexed depositedToken ); event ReverseSwapOccurred( address indexed user, address indexed rsETH, address indexed token, uint256 rsETHAmount, uint256 tokenAmount ); event FeesWithdrawn(uint256 feeEarnedInETH); event FeesWithdrawn(uint256 feeEarnedInETH, address token); event BridgedETHToL1ViaNativeBridge(address indexed l1Receiver, uint256 ethBalanceMinusFees); event BridgedETHToL1( uint32 indexed lzChainId, address indexed l1Receiver, uint256 amountSent, uint256 amountReceived ); event BridgedTokenToL1(address indexed token, address indexed l1Receiver, uint256 amountSent); event FeeBpsSet(uint256 feeBps); event OracleSet(address oracle); event AddSupportedToken(address token, address oracle, address bridge); event RemovedSupportedToken(address token); event L1VaultETHForL2ChainSet(address l1VaultETHForL2Chain); event StargatePoolSet(address stargatePool); event LzChainIdSet(uint32 lzChainId); event Paused(address account); event Unpaused(address account); event DailyMintLimitSet(uint256 dailyMintLimit); event TokenOracleSet(address indexed token, address oracle); event TokenBridgeSet(address indexed token, address bridge); event L2BridgeSet(address l2Bridge); event MessengerSet(address messenger); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } /** * @dev Version history: * - reinitializer(1): Initial deployment and setup of the contract with initial roles, wrsETH token, fees and * oracles. Only native ETH deposits used to be supported. * - reinitializer(2): Added support for ETH bridging via Stargate from L2 to L1. * - reinitializer(3): Upgraded to update the LayerZero V2 chain ID for the ETH mainnet. * - reinitializer(4): Introduced daily minting limit functionality to control the amount of wrsETH that can * be minted per day. * - reinitializer(5): Added a new supported token (wstETH), along with the oracle and the native bridging logic for * it. * - reinitializer(6): This upgrade enables native bridging of ETH from L2 to L1. */ /** * @notice Reinitializes the contract to enable native bridging of ETH * @param _l2Bridge The address of the L2 bridge contract * @param _messenger The address of the L2 messenger contract */ function reinitialize(address _l2Bridge, address _messenger) external reinitializer(6) onlyRole(DEFAULT_ADMIN_ROLE) { UtilLib.checkNonZeroAddress(_l2Bridge); UtilLib.checkNonZeroAddress(_messenger); l2Bridge = _l2Bridge; messenger = _messenger; emit L2BridgeSet(_l2Bridge); emit MessengerSet(_messenger); } /// @dev Reinitializer function to set the initial supported token /// @param token The token address /// @param oracle The oracle address /// @param bridge The bridge address function reinitialize( address token, address oracle, address bridge ) external reinitializer(5) onlyRole(DEFAULT_ADMIN_ROLE) { _addSupportedToken(token, oracle, bridge); } /// @dev Reinitializer function to set the daily minting limit /// @param _dailyMintLimit The daily minting limit /// @param _startTimestamp The start timestamp for the daily minting limit function reinitialize( uint256 _dailyMintLimit, uint256 _startTimestamp ) external reinitializer(4) onlyRole(DEFAULT_ADMIN_ROLE) { if (_dailyMintLimit == 0) { revert InvalidDailyMintLimit(); } // startTimestamp cannot be in the past if (block.timestamp > _startTimestamp) { revert InvalidStartTimestamp(); } dailyMintLimit = _dailyMintLimit; startTimestamp = _startTimestamp; } /// @dev Reinitialize the contract /// @param _dstLzChainId The LayerZero ID for the ETH mainnet function reinitialize(uint32 _dstLzChainId) external reinitializer(3) onlyRole(DEFAULT_ADMIN_ROLE) { dstLzChainId = _dstLzChainId; } /// @dev Reinitialize the contract /// @param _l1VaultETHForL2Chain The address of the L1VaultETH for the L2 chain /// @param _stargatePool The address of the StargatePool used for L2 --> L1 bridging /// @param _dstLzChainId The LayerZero ID for the ETH mainnet function reinitialize( address _l1VaultETHForL2Chain, address _stargatePool, uint32 _dstLzChainId ) external reinitializer(2) onlyRole(DEFAULT_ADMIN_ROLE) { UtilLib.checkNonZeroAddress(_l1VaultETHForL2Chain); UtilLib.checkNonZeroAddress(_stargatePool); l1VaultETHForL2Chain = _l1VaultETHForL2Chain; stargatePool = IStargatePoolNative(_stargatePool); dstLzChainId = _dstLzChainId; } /// @dev Initialize the contract /// @param admin The admin address /// @param bridger The bridger address /// @param _wrsETH The rsETH token address /// @param _feeBps The fee basis points /// @param _rsETHOracle The rsETHOracle address function initialize( address admin, address bridger, address _wrsETH, uint256 _feeBps, address _rsETHOracle ) external initializer { UtilLib.checkNonZeroAddress(_wrsETH); UtilLib.checkNonZeroAddress(_rsETHOracle); __ERC20_init("rsETH", "rsETH"); __AccessControl_init(); __ReentrancyGuard_init(); _grantRole(DEFAULT_ADMIN_ROLE, admin); _setupRole(BRIDGER_ROLE, admin); _setupRole(BRIDGER_ROLE, bridger); wrsETH = IERC20WrsETH(_wrsETH); feeBps = _feeBps; rsETHOracle = _rsETHOracle; } /// @dev Gets the rate from the rsETHOracle function getRate() public view returns (uint256) { return IOracle(rsETHOracle).getRate(); } /// @dev Returns the list of supported tokens function getSupportedTokens() external view returns (address[] memory) { return supportedTokenList; } /// @dev Swaps ETH for rsETH /// @param referralId The referral id function deposit(string memory referralId) external payable nonReentrant whenNotPaused limitDailyMint(msg.value, ETH_IDENTIFIER) { uint256 amount = msg.value; if (amount == 0) revert InvalidAmount(); (uint256 rsETHAmount, uint256 fee) = viewSwapRsETHAmountAndFee(amount); feeEarnedInETH += fee; wrsETH.mint(msg.sender, rsETHAmount); emit SwapOccurred(msg.sender, rsETHAmount, fee, referralId); } /// @dev Swaps supported token for rsETH /// @param token The token address /// @param amount The amount of token /// @param referralId The referral id function deposit( address token, uint256 amount, string memory referralId ) external nonReentrant whenNotPaused onlySupportedToken(token) limitDailyMint(amount, token) { if (amount == 0) revert InvalidAmount(); IERC20(token).safeTransferFrom(msg.sender, address(this), amount); (uint256 rsETHAmount, uint256 fee) = viewSwapRsETHAmountAndFee(amount, token); feeEarnedInToken[token] += fee; wrsETH.mint(msg.sender, rsETHAmount); emit SwapOccurred(msg.sender, rsETHAmount, fee, referralId, token); } /// @dev view function to get the rsETH amount for a given amount of ETH /// @param amount The amount of ETH /// @return rsETHAmount The amount of rsETH that will be received /// @return fee The fee that will be charged function viewSwapRsETHAmountAndFee(uint256 amount) public view returns (uint256 rsETHAmount, uint256 fee) { fee = amount * feeBps / 10_000; uint256 amountAfterFee = amount - fee; // rate of rsETH in ETH uint256 rsETHToETHrate = getRate(); // Calculate the final rsETH amount rsETHAmount = amountAfterFee * 1e18 / rsETHToETHrate; } /// @dev view function to get the rsETH amount for a given amount of token /// @param amount The amount of token /// @return rsETHAmount The amount of rsETH that will be received /// @return fee The fee that will be charged function viewSwapRsETHAmountAndFee( uint256 amount, address token ) public view onlySupportedToken(token) returns (uint256 rsETHAmount, uint256 fee) { fee = amount * feeBps / 10_000; uint256 amountAfterFee = amount - fee; // rate of rsETH in ETH uint256 rsETHToETHrate = getRate(); // rate of token in ETH uint256 tokenToETHRate = IOracle(supportedTokenOracle[token]).getRate(); // Calculate the final rsETH amount rsETHAmount = amountAfterFee * tokenToETHRate / rsETHToETHrate; } /** * @dev Quote the native fee for sending ETH to L1 * @param amount The amount of ETH to send * @param minAmount The minimum amount of ETH to receive on L1 after slippage * @return The fee to be paid in native currency */ function getNativeFee(uint256 amount, uint256 minAmount) external view returns (uint256) { if (minAmount > amount || minAmount == 0) { revert InvalidMinAmount(); } SendParam memory sendParam = SendParam({ dstEid: dstLzChainId, to: getReceiver(), amountLD: amount, minAmountLD: minAmount, extraOptions: bytes(""), composeMsg: bytes(""), oftCmd: bytes("") }); MessagingFee memory fee = stargatePool.quoteSend(sendParam, false); return fee.nativeFee; } /** * @dev Get the receiver address in the bytes32 format * @return The receiver address in the bytes32 format */ function getReceiver() public view returns (bytes32) { return bytes32(uint256(uint160(l1VaultETHForL2Chain))); } /** * @dev Get the ETH balance minus the fees * @return The ETH balance minus the fees */ function getETHBalanceMinusFees() public view returns (uint256) { return address(this).balance - feeEarnedInETH; } /** * @dev Get the token balance minus the fees * @param token The token address * @return The token balance minus the fees */ function getTokenBalanceMinusFees(address token) public view returns (uint256) { return IERC20(token).balanceOf(address(this)) - feeEarnedInToken[token]; } /** * @notice View quote for swapping minted rsETH to a supported pool asset * @dev Functionally, it works as the opposite of `viewSwapRsETHAmountAndFee` * @param token Requested output asset (supported asset in the pool) * @param rsETHAmount Amount of rsETH to swap. * @return tokenAmount Amount of `token` the caller would receive. */ function viewSwapAssetToPremintedRsETH( address token, uint256 rsETHAmount ) public view onlySupportedTokenOrEth(token) returns (uint256 tokenAmount) { // Rate of rsETH in ETH uint256 rsETHToETHrate = getRate(); if (rsETHToETHrate == 0) revert UnsupportedOracle(); // Rate of token in ETH uint256 tokenToETHRate = token == ETH_IDENTIFIER ? 1e18 : IOracle(supportedTokenOracle[token]).getRate(); if (tokenToETHRate == 0) revert UnsupportedOracle(); // Calculate the amount of token user will get for the amount of rsETH tokenAmount = rsETHAmount * rsETHToETHrate / tokenToETHRate; } /** * @dev Get the minimum amount after slippage * @param amount The amount * @param slippageTolerance The slippage tolerance * @return The minimum amount after slippage */ function getMinAmount(uint256 amount, uint256 slippageTolerance) external pure returns (uint256) { if (slippageTolerance > 10_000) revert InvalidSlippageTolerance(); return amount - (amount * slippageTolerance / 10_000); } /// @notice Gets the current day relative to the start timestamp /// @return uint256 The current day relative to the start timestamp function getCurrentDay() public view returns (uint256) { return (block.timestamp - startTimestamp) / 1 days; } /// @notice Gets the remaining daily minting limit /// @return uint256 The remaining daily minting limit function remainingDailyMintLimit() external view returns (uint256) { // If we're on a new day but no mint has occurred yet, treat dailyMintAmount as 0 uint256 effectiveDailyMintAmount = (getCurrentDay() > lastMintDay) ? 0 : dailyMintAmount; return dailyMintLimit - effectiveDailyMintAmount; } /// @notice Gets the next daily mint limit reset timestamp /// @return uint256 The next daily mint limit reset timestamp function getNextDailyLimitResetTimestamp() external view returns (uint256) { return startTimestamp + (getCurrentDay() + 1) * 1 days; } /*////////////////////////////////////////////////////////////// ACCESS RESTRICTED FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @notice Operator-only swap from minted rsETH to a supported pool asset * @dev Functionally, it works as the opposite of `deposit`, but it does not charge any fees * @param rsETH Address of the rsETH token on this chain (must be allowed in wrapper) * @param token Requested output asset (ETH_IDENTIFIER for native ETH, or a supported ERC20 token) * @param rsETHAmount Amount of rsETH to swap for `token` */ function swapAssetToPremintedRsETH( address rsETH, address token, uint256 rsETHAmount ) external nonReentrant onlySupportedTokenOrEth(token) onlyRole(OPERATOR_ROLE) { UtilLib.checkNonZeroAddress(rsETH); IRsETHTokenWrapper wrapper = IRsETHTokenWrapper(address(wrsETH)); IERC20 tokenContract = IERC20(token); if (!wrapper.allowedTokens(rsETH)) revert TokenNotAllowedInWrapper(); if (rsETHAmount == 0) revert InvalidAmount(); if (rsETHAmount > wrapper.maxAmountToDepositBridgerAsset(rsETH)) revert ExceedsMaxAmountToDepositInWrapper(); // Get the amount of token to transfer to the user for the given amount of rsETH provided uint256 tokenAmount = viewSwapAssetToPremintedRsETH(token, rsETHAmount); // Transfer rsETH from sender to the wrapper IERC20(rsETH).safeTransferFrom(msg.sender, address(wrapper), rsETHAmount); // Transfer the token from the pool to the sender if (token == ETH_IDENTIFIER) { if (getETHBalanceMinusFees() < tokenAmount) revert InsufficientETHBalanceForReverseSwap(); (bool success,) = payable(msg.sender).call{ value: tokenAmount }(""); if (!success) revert TransferFailed(); } else { if (getTokenBalanceMinusFees(token) < tokenAmount) revert InsufficientAssetBalanceForReverseSwap(); tokenContract.safeTransfer(msg.sender, tokenAmount); } emit ReverseSwapOccurred(msg.sender, rsETH, token, rsETHAmount, tokenAmount); } /// @dev Withdraws fees earned by the pool in ETH function withdrawFees(address receiver) external nonReentrant onlyRole(BRIDGER_ROLE) { // withdraw fees in ETH uint256 amountToSendInETH = feeEarnedInETH; feeEarnedInETH = 0; (bool success,) = payable(receiver).call{ value: amountToSendInETH }(""); if (!success) revert TransferFailed(); emit FeesWithdrawn(amountToSendInETH); } /// @dev Withdraws fees earned by the pool in a specific token function withdrawFees( address receiver, address token ) external nonReentrant onlySupportedToken(token) onlyRole(BRIDGER_ROLE) { // withdraw fees in token uint256 amountToSendInToken = feeEarnedInToken[token]; feeEarnedInToken[token] = 0; IERC20(token).safeTransfer(receiver, amountToSendInToken); emit FeesWithdrawn(amountToSendInToken, token); } /// @dev Legacy function - Withdraws assets from the contract for bridging function moveAssetsForBridging() external view onlyRole(BRIDGER_ROLE) { revert DeprecatedFunction(); } /// @notice Withdraws ETH from L2 to L1 using the L2's native bridge /// @param amount The amount of ETH to bridge via the native bridge function bridgeAssetsViaNativeBridge(uint256 amount) external nonReentrant onlyRole(BRIDGER_ROLE) { UtilLib.checkNonZeroAddress(l2Bridge); UtilLib.checkNonZeroAddress(messenger); UtilLib.checkNonZeroAddress(l1VaultETHForL2Chain); if (amount == 0) revert InvalidAmount(); // bridge up to the ETH balance minus fees uint256 ethBalanceMinusFees = getETHBalanceMinusFees(); if (amount > ethBalanceMinusFees) revert InsufficientETHBalance(); IL2Messenger(messenger).sendETHToL1ViaBridge{ value: amount }(l2Bridge, l1VaultETHForL2Chain, amount); emit BridgedETHToL1ViaNativeBridge(l1VaultETHForL2Chain, amount); } /// @dev Withdraws ETH from the L2 to L1 using LayerZero /// @param amount The amount of ETH to bridge /// @param minAmount The minimum amount of ETH to receive on L1 /// @param nativeFee The native fee to pay for the bridge function bridgeAssets( uint256 amount, uint256 minAmount, uint256 nativeFee ) external payable nonReentrant onlyRole(BRIDGER_ROLE) { // Exclude msg.value so reserved fees can’t be accidentally consumed if (getETHBalanceMinusFees() - msg.value < amount) { revert InsufficientETHBalance(); } if (minAmount > amount || minAmount == 0) { revert InvalidMinAmount(); } if (msg.value != nativeFee) { revert IncorrectNativeFee(); } SendParam memory sendParam = SendParam({ dstEid: dstLzChainId, to: getReceiver(), amountLD: amount, minAmountLD: minAmount, extraOptions: bytes(""), composeMsg: bytes(""), oftCmd: bytes("") }); MessagingFee memory fee = MessagingFee({ nativeFee: nativeFee, lzTokenFee: 0 }); (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = stargatePool.send{ value: nativeFee + amount }(sendParam, fee, msg.sender); latestTxReceipt = TxReceipt({ guid: msgReceipt.guid, amountReceivedLD: oftReceipt.amountReceivedLD }); emit BridgedETHToL1(dstLzChainId, l1VaultETHForL2Chain, oftReceipt.amountSentLD, oftReceipt.amountReceivedLD); } /// @dev Bridges tokens to L1 using their corresponding token bridge /// @param token The address of the token to bridge function bridgeTokens(address token) external payable nonReentrant onlySupportedToken(token) onlyRole(BRIDGER_ROLE) { if (tokenBridge[token] == address(0)) { revert MissingBridgeForToken(); } uint256 balance = getTokenBalanceMinusFees(token); if (balance == 0) { revert ZeroBridgeAmount(); } // Approve the required amount to the bridge IERC20(token).safeIncreaseAllowance(tokenBridge[token], balance); // Call the bridge contract to transfer the tokens (msg.value is included in case we need to pay for additional // bridging fees) IL2TokenBridge(tokenBridge[token]).bridgeTokenToL1{ value: msg.value }(l1VaultETHForL2Chain, balance); emit BridgedTokenToL1(token, l1VaultETHForL2Chain, balance); } /// @dev Sets the fee basis points /// @param _feeBps The fee basis points function setFeeBps(uint256 _feeBps) external onlyRole(DEFAULT_ADMIN_ROLE) { if (_feeBps > 10_000) revert InvalidFeeAmount(); feeBps = _feeBps; emit FeeBpsSet(_feeBps); } /// @dev Sets the rsETHOracle address /// @param _rsETHOracle The rsETHOracle address function setRSETHOracle(address _rsETHOracle) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_rsETHOracle); rsETHOracle = _rsETHOracle; emit OracleSet(_rsETHOracle); } /// @dev Adds a supported token /// @param token The token address /// @param oracle The oracle address /// @param bridge The bridge address function addSupportedToken(address token, address oracle, address bridge) external onlyRole(TIMELOCK_ROLE) { _addSupportedToken(token, oracle, bridge); } /// @dev Removes a supported token /// @param token The token address /// @param tokenIndex The index of the token in the supportedTokenList function removeSupportedToken(address token, uint256 tokenIndex) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(token); if (supportedTokenList[tokenIndex] != token) revert TokenNotFoundError(); if (IERC20(token).balanceOf(address(this)) != 0) revert TokenBalanceNotZero(); delete supportedTokenOracle[token]; delete tokenBridge[token]; supportedTokenList[tokenIndex] = supportedTokenList[supportedTokenList.length - 1]; supportedTokenList.pop(); emit RemovedSupportedToken(token); } /// @dev Sets the new L1VaultETH for the L2 chain /// @param _l1VaultETHForL2Chain The new L1VaultETH for the L2 chain function setL1VaultETHForL2Chain(address _l1VaultETHForL2Chain) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_l1VaultETHForL2Chain); l1VaultETHForL2Chain = _l1VaultETHForL2Chain; emit L1VaultETHForL2ChainSet(_l1VaultETHForL2Chain); } /// @dev Sets the new stargatePool address /// @param _stargatePool The new stargatePool address function setStargatePool(address _stargatePool) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_stargatePool); stargatePool = IStargatePoolNative(_stargatePool); emit StargatePoolSet(_stargatePool); } /// @dev Sets the destination LayerZero chain ID /// @param _dstLzChainId The destination LayerZero chain ID function setDstLzChainId(uint32 _dstLzChainId) external onlyRole(TIMELOCK_ROLE) { if (_dstLzChainId == 0) { revert InvalidLzChainId(); } dstLzChainId = _dstLzChainId; emit LzChainIdSet(_dstLzChainId); } /** * @notice Sets the oracle for a specific token * @param token The token address * @param oracle The new oracle address for the token */ function setSupportedTokenOracle( address token, address oracle ) external onlyRole(TIMELOCK_ROLE) onlySupportedToken(token) { UtilLib.checkNonZeroAddress(oracle); if (IOracle(oracle).getRate() == 0) { revert UnsupportedOracle(); } supportedTokenOracle[token] = oracle; emit TokenOracleSet(token, oracle); } /** * @notice Sets the token bridge address for a specific token * @param token The token address * @param bridge The new bridge address for the token */ function setTokenBridge(address token, address bridge) external onlyRole(TIMELOCK_ROLE) onlySupportedToken(token) { UtilLib.checkNonZeroAddress(bridge); tokenBridge[token] = bridge; emit TokenBridgeSet(token, bridge); } /** * @notice Sets the new l2Bridge address * @param _l2Bridge The new l2Bridge address */ function setL2Bridge(address _l2Bridge) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_l2Bridge); l2Bridge = _l2Bridge; emit L2BridgeSet(_l2Bridge); } /** * @notice Sets the L2 messenger address * @param _messenger The new L2 messenger address */ function setMessenger(address _messenger) external onlyRole(TIMELOCK_ROLE) { UtilLib.checkNonZeroAddress(_messenger); messenger = _messenger; emit MessengerSet(_messenger); } /// @dev Pauses the pausable methods in the contract function pause() external onlyRole(PAUSER_ROLE) whenNotPaused { paused = true; emit Paused(msg.sender); } /// @dev Unpauses the pausable methods in the contract function unpause() external onlyRole(DEFAULT_ADMIN_ROLE) whenPaused { paused = false; emit Unpaused(msg.sender); } /// @dev Sets the daily minting limit /// @param _dailyMintLimit The new daily minting limit function setDailyMintLimit(uint256 _dailyMintLimit) external onlyRole(DEFAULT_ADMIN_ROLE) { if (_dailyMintLimit == 0) { revert InvalidDailyMintLimit(); } dailyMintLimit = _dailyMintLimit; emit DailyMintLimitSet(_dailyMintLimit); } /// @dev Internal function to add a supported token function _addSupportedToken(address token, address oracle, address bridge) internal { UtilLib.checkNonZeroAddress(token); UtilLib.checkNonZeroAddress(oracle); UtilLib.checkNonZeroAddress(bridge); if (supportedTokenOracle[token] != address(0)) { revert AlreadySupportedToken(); } if (tokenBridge[token] != address(0)) { revert AlreadySupportedToken(); } if (IOracle(oracle).getRate() == 0) { revert UnsupportedOracle(); } supportedTokenList.push(token); supportedTokenOracle[token] = oracle; tokenBridge[token] = bridge; emit AddSupportedToken(token, oracle, bridge); } }
비교하기