Router.sol 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. // SPDX-License-Identifier: BUSL-1.1
  2. pragma solidity 0.7.6;
  3. pragma abicoder v2;
  4. // imports
  5. import "@openzeppelin/contracts/access/Ownable.sol";
  6. import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
  7. import "./Factory.sol";
  8. import "./Pool.sol";
  9. import "./Bridge.sol";
  10. // interfaces
  11. import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
  12. import "./interfaces/IMirrorgateRouter.sol";
  13. import "./interfaces/IMirrorgateReceiver.sol";
  14. // libraries
  15. import "@openzeppelin/contracts/math/SafeMath.sol";
  16. contract Router is IMirrorgateRouter, Ownable, ReentrancyGuard {
  17. using SafeMath for uint256;
  18. //---------------------------------------------------------------------------
  19. // CONSTANTS
  20. uint8 internal constant TYPE_REDEEM_LOCAL_RESPONSE = 1;
  21. uint8 internal constant TYPE_REDEEM_LOCAL_CALLBACK_RETRY = 2;
  22. uint8 internal constant TYPE_SWAP_REMOTE_RETRY = 3;
  23. //---------------------------------------------------------------------------
  24. // STRUCTS
  25. struct CachedSwap {
  26. address token;
  27. uint256 amountLD;
  28. address to;
  29. bytes payload;
  30. }
  31. //---------------------------------------------------------------------------
  32. // VARIABLES
  33. Factory public factory; // used for creating pools
  34. address public protocolFeeOwner; // can call methods to pull Stargate fees collected in pools
  35. address public mintFeeOwner; // can call methods to pull mint fees collected in pools
  36. Bridge public bridge;
  37. mapping(uint16 => mapping(bytes => mapping(uint256 => bytes))) public revertLookup; //[chainId][srcAddress][nonce]
  38. mapping(uint16 => mapping(bytes => mapping(uint256 => CachedSwap))) public cachedSwapLookup; //[chainId][srcAddress][nonce]
  39. //---------------------------------------------------------------------------
  40. // EVENTS
  41. event Revert(uint8 bridgeFunctionType, uint16 chainId, bytes srcAddress, uint256 nonce);
  42. event CachedSwapSaved(uint16 chainId, bytes srcAddress, uint256 nonce, address token, uint256 amountLD, address to, bytes payload, bytes reason);
  43. event RevertRedeemLocal(uint16 srcChainId, uint256 _srcPoolId, uint256 _dstPoolId, bytes to, uint256 redeemAmountSD, uint256 mintAmountSD, uint256 indexed nonce, bytes indexed srcAddress);
  44. event RedeemLocalCallback(uint16 srcChainId, bytes indexed srcAddress, uint256 indexed nonce, uint256 srcPoolId, uint256 dstPoolId, address to, uint256 amountSD, uint256 mintAmountSD);
  45. //---------------------------------------------------------------------------
  46. // MODIFIERS
  47. modifier onlyBridge() {
  48. require(msg.sender == address(bridge), "Bridge: caller must be Bridge.");
  49. _;
  50. }
  51. constructor() {}
  52. function setBridgeAndFactory(Bridge _bridge, Factory _factory) external onlyOwner {
  53. require(address(bridge) == address(0x0) && address(factory) == address(0x0), "Mirrorgate: bridge and factory already initialized"); // 1 time only
  54. require(address(_bridge) != address(0x0), "Mirrorgate: bridge cant be 0x0");
  55. require(address(_factory) != address(0x0), "Mirrorgate: factory cant be 0x0");
  56. bridge = _bridge;
  57. factory = _factory;
  58. }
  59. //---------------------------------------------------------------------------
  60. // VIEWS
  61. function _getPool(uint256 _poolId) internal view returns (Pool pool) {
  62. pool = factory.getPool(_poolId);
  63. require(address(pool) != address(0x0), "Mirrorgate: Pool does not exist");
  64. }
  65. //---------------------------------------------------------------------------
  66. // INTERNAL
  67. function _safeTransferFrom(
  68. address token,
  69. address from,
  70. address to,
  71. uint256 value
  72. ) private {
  73. // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
  74. (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
  75. require(success && (data.length == 0 || abi.decode(data, (bool))), "Mirrorgate: TRANSFER_FROM_FAILED");
  76. }
  77. //---------------------------------------------------------------------------
  78. // LOCAL CHAIN FUNCTIONS
  79. function addLiquidity(
  80. uint256 _poolId,
  81. uint256 _amountLD,
  82. address _to
  83. ) external override nonReentrant {
  84. Pool pool = _getPool(_poolId);
  85. uint256 convertRate = pool.convertRate();
  86. _amountLD = _amountLD.div(convertRate).mul(convertRate);
  87. _safeTransferFrom(pool.token(), msg.sender, address(pool), _amountLD);
  88. pool.mint(_to, _amountLD);
  89. }
  90. function swap(
  91. uint16 _dstChainId,
  92. uint256 _srcPoolId,
  93. uint256 _dstPoolId,
  94. address payable _refundAddress,
  95. uint256 _amountLD,
  96. uint256 _minAmountLD,
  97. lzTxObj memory _lzTxParams,
  98. bytes calldata _to,
  99. bytes calldata _payload
  100. ) external payable override nonReentrant {
  101. require(_amountLD > 0, "Mirrorgate: cannot swap 0");
  102. require(_refundAddress != address(0x0), "Mirrorgate: _refundAddress cannot be 0x0");
  103. Pool.SwapObj memory s;
  104. Pool.CreditObj memory c;
  105. {
  106. Pool pool = _getPool(_srcPoolId);
  107. {
  108. uint256 convertRate = pool.convertRate();
  109. _amountLD = _amountLD.div(convertRate).mul(convertRate);
  110. }
  111. s = pool.swap(_dstChainId, _dstPoolId, msg.sender, _amountLD, _minAmountLD, true);
  112. _safeTransferFrom(pool.token(), msg.sender, address(pool), _amountLD);
  113. c = pool.sendCredits(_dstChainId, _dstPoolId);
  114. }
  115. bridge.swap{value: msg.value}(_dstChainId, _srcPoolId, _dstPoolId, _refundAddress, c, s, _lzTxParams, _to, _payload);
  116. }
  117. function redeemRemote(
  118. uint16 _dstChainId,
  119. uint256 _srcPoolId,
  120. uint256 _dstPoolId,
  121. address payable _refundAddress,
  122. uint256 _amountLP,
  123. uint256 _minAmountLD,
  124. bytes calldata _to,
  125. lzTxObj memory _lzTxParams
  126. ) external payable override nonReentrant {
  127. require(_refundAddress != address(0x0), "Mirrorgate: _refundAddress cannot be 0x0");
  128. require(_amountLP > 0, "Mirrorgate: not enough lp to redeemRemote");
  129. Pool.SwapObj memory s;
  130. Pool.CreditObj memory c;
  131. {
  132. Pool pool = _getPool(_srcPoolId);
  133. uint256 amountLD = pool.amountLPtoLD(_amountLP);
  134. // perform a swap with no liquidity
  135. s = pool.swap(_dstChainId, _dstPoolId, msg.sender, amountLD, _minAmountLD, false);
  136. pool.redeemRemote(_dstChainId, _dstPoolId, msg.sender, _amountLP);
  137. c = pool.sendCredits(_dstChainId, _dstPoolId);
  138. }
  139. // equal to a swap, with no payload ("0x") no dstGasForCall 0
  140. bridge.swap{value: msg.value}(_dstChainId, _srcPoolId, _dstPoolId, _refundAddress, c, s, _lzTxParams, _to, "");
  141. }
  142. function instantRedeemLocal(
  143. uint16 _srcPoolId,
  144. uint256 _amountLP,
  145. address _to
  146. ) external override nonReentrant returns (uint256 amountSD) {
  147. require(_amountLP > 0, "Mirrorgate: not enough lp to redeem");
  148. Pool pool = _getPool(_srcPoolId);
  149. amountSD = pool.instantRedeemLocal(msg.sender, _amountLP, _to);
  150. }
  151. function redeemLocal(
  152. uint16 _dstChainId,
  153. uint256 _srcPoolId,
  154. uint256 _dstPoolId,
  155. address payable _refundAddress,
  156. uint256 _amountLP,
  157. bytes calldata _to,
  158. lzTxObj memory _lzTxParams
  159. ) external payable override nonReentrant {
  160. require(_refundAddress != address(0x0), "Mirrorgate: _refundAddress cannot be 0x0");
  161. Pool pool = _getPool(_srcPoolId);
  162. require(_amountLP > 0, "Mirrorgate: not enough lp to redeem");
  163. uint256 amountSD = pool.redeemLocal(msg.sender, _amountLP, _dstChainId, _dstPoolId, _to);
  164. require(amountSD > 0, "Mirrorgate: not enough lp to redeem with amountSD");
  165. Pool.CreditObj memory c = pool.sendCredits(_dstChainId, _dstPoolId);
  166. bridge.redeemLocal{value: msg.value}(_dstChainId, _srcPoolId, _dstPoolId, _refundAddress, c, amountSD, _to, _lzTxParams);
  167. }
  168. function sendCredits(
  169. uint16 _dstChainId,
  170. uint256 _srcPoolId,
  171. uint256 _dstPoolId,
  172. address payable _refundAddress
  173. ) external payable override nonReentrant {
  174. require(_refundAddress != address(0x0), "Mirrorgate: _refundAddress cannot be 0x0");
  175. Pool pool = _getPool(_srcPoolId);
  176. Pool.CreditObj memory c = pool.sendCredits(_dstChainId, _dstPoolId);
  177. bridge.sendCredits{value: msg.value}(_dstChainId, _srcPoolId, _dstPoolId, _refundAddress, c);
  178. }
  179. function quoteLayerZeroFee(
  180. uint16 _dstChainId,
  181. uint8 _functionType,
  182. bytes calldata _toAddress,
  183. bytes calldata _transferAndCallPayload,
  184. Router.lzTxObj memory _lzTxParams
  185. ) external view override returns (uint256, uint256) {
  186. return bridge.quoteLayerZeroFee(_dstChainId, _functionType, _toAddress, _transferAndCallPayload, _lzTxParams);
  187. }
  188. function revertRedeemLocal(
  189. uint16 _dstChainId,
  190. bytes calldata _srcAddress,
  191. uint256 _nonce,
  192. address payable _refundAddress,
  193. lzTxObj memory _lzTxParams
  194. ) external payable {
  195. require(_refundAddress != address(0x0), "Mirrorgate: _refundAddress cannot be 0x0");
  196. bytes memory payload = revertLookup[_dstChainId][_srcAddress][_nonce];
  197. require(payload.length > 0, "Mirrorgate: no retry revert");
  198. {
  199. uint8 functionType;
  200. assembly {
  201. functionType := mload(add(payload, 32))
  202. }
  203. require(functionType == TYPE_REDEEM_LOCAL_RESPONSE, "Mirrorgate: invalid function type");
  204. }
  205. // empty it
  206. revertLookup[_dstChainId][_srcAddress][_nonce] = "";
  207. uint256 srcPoolId;
  208. uint256 dstPoolId;
  209. assembly {
  210. srcPoolId := mload(add(payload, 64))
  211. dstPoolId := mload(add(payload, 96))
  212. }
  213. Pool.CreditObj memory c;
  214. {
  215. Pool pool = _getPool(dstPoolId);
  216. c = pool.sendCredits(_dstChainId, srcPoolId);
  217. }
  218. bridge.redeemLocalCallback{value: msg.value}(_dstChainId, _refundAddress, c, _lzTxParams, payload);
  219. }
  220. function retryRevert(
  221. uint16 _srcChainId,
  222. bytes calldata _srcAddress,
  223. uint256 _nonce
  224. ) external payable {
  225. bytes memory payload = revertLookup[_srcChainId][_srcAddress][_nonce];
  226. require(payload.length > 0, "Mirrorgate: no retry revert");
  227. // empty it
  228. revertLookup[_srcChainId][_srcAddress][_nonce] = "";
  229. uint8 functionType;
  230. assembly {
  231. functionType := mload(add(payload, 32))
  232. }
  233. if (functionType == TYPE_REDEEM_LOCAL_CALLBACK_RETRY) {
  234. (, uint256 srcPoolId, uint256 dstPoolId, address to, uint256 amountSD, uint256 mintAmountSD) = abi.decode(
  235. payload,
  236. (uint8, uint256, uint256, address, uint256, uint256)
  237. );
  238. _redeemLocalCallback(_srcChainId, _srcAddress, _nonce, srcPoolId, dstPoolId, to, amountSD, mintAmountSD);
  239. }
  240. // for retrying the swapRemote. if it fails again, retry
  241. else if (functionType == TYPE_SWAP_REMOTE_RETRY) {
  242. (, uint256 srcPoolId, uint256 dstPoolId, uint256 dstGasForCall, address to, Pool.SwapObj memory s, bytes memory p) = abi.decode(
  243. payload,
  244. (uint8, uint256, uint256, uint256, address, Pool.SwapObj, bytes)
  245. );
  246. _swapRemote(_srcChainId, _srcAddress, _nonce, srcPoolId, dstPoolId, dstGasForCall, to, s, p);
  247. } else {
  248. revert("Mirrorgate: invalid function type");
  249. }
  250. }
  251. function clearCachedSwap(
  252. uint16 _srcChainId,
  253. bytes calldata _srcAddress,
  254. uint256 _nonce
  255. ) external {
  256. CachedSwap memory cs = cachedSwapLookup[_srcChainId][_srcAddress][_nonce];
  257. require(cs.to != address(0x0), "Mirrorgate: cache already cleared");
  258. // clear the data
  259. cachedSwapLookup[_srcChainId][_srcAddress][_nonce] = CachedSwap(address(0x0), 0, address(0x0), "");
  260. IMirrorgateReceiver(cs.to).mgReceive(_srcChainId, _srcAddress, _nonce, cs.token, cs.amountLD, cs.payload);
  261. }
  262. function creditChainPath(
  263. uint16 _dstChainId,
  264. uint256 _dstPoolId,
  265. uint256 _srcPoolId,
  266. Pool.CreditObj memory _c
  267. ) external onlyBridge {
  268. Pool pool = _getPool(_srcPoolId);
  269. pool.creditChainPath(_dstChainId, _dstPoolId, _c);
  270. }
  271. //---------------------------------------------------------------------------
  272. // REMOTE CHAIN FUNCTIONS
  273. function redeemLocalCheckOnRemote(
  274. uint16 _srcChainId,
  275. bytes memory _srcAddress,
  276. uint256 _nonce,
  277. uint256 _srcPoolId,
  278. uint256 _dstPoolId,
  279. uint256 _amountSD,
  280. bytes calldata _to
  281. ) external onlyBridge {
  282. Pool pool = _getPool(_dstPoolId);
  283. try pool.redeemLocalCheckOnRemote(_srcChainId, _srcPoolId, _amountSD) returns (uint256 redeemAmountSD, uint256 mintAmountSD) {
  284. revertLookup[_srcChainId][_srcAddress][_nonce] = abi.encode(
  285. TYPE_REDEEM_LOCAL_RESPONSE,
  286. _srcPoolId,
  287. _dstPoolId,
  288. redeemAmountSD,
  289. mintAmountSD,
  290. _to
  291. );
  292. emit RevertRedeemLocal(_srcChainId, _srcPoolId, _dstPoolId, _to, redeemAmountSD, mintAmountSD, _nonce, _srcAddress);
  293. } catch {
  294. // if the func fail, return [swapAmount: 0, mintAMount: _amountSD]
  295. // swapAmount represents the amount of chainPath balance deducted on the remote side, which because the above tx failed, should be 0
  296. // mintAmount is the full amount of tokens the user attempted to redeem on the src side, which gets converted back into the lp amount
  297. revertLookup[_srcChainId][_srcAddress][_nonce] = abi.encode(TYPE_REDEEM_LOCAL_RESPONSE, _srcPoolId, _dstPoolId, 0, _amountSD, _to);
  298. emit Revert(TYPE_REDEEM_LOCAL_RESPONSE, _srcChainId, _srcAddress, _nonce);
  299. }
  300. }
  301. function redeemLocalCallback(
  302. uint16 _srcChainId,
  303. bytes memory _srcAddress,
  304. uint256 _nonce,
  305. uint256 _srcPoolId,
  306. uint256 _dstPoolId,
  307. address _to,
  308. uint256 _amountSD,
  309. uint256 _mintAmountSD
  310. ) external onlyBridge {
  311. _redeemLocalCallback(_srcChainId, _srcAddress, _nonce, _srcPoolId, _dstPoolId, _to, _amountSD, _mintAmountSD);
  312. }
  313. function _redeemLocalCallback(
  314. uint16 _srcChainId,
  315. bytes memory _srcAddress,
  316. uint256 _nonce,
  317. uint256 _srcPoolId,
  318. uint256 _dstPoolId,
  319. address _to,
  320. uint256 _amountSD,
  321. uint256 _mintAmountSD
  322. ) internal {
  323. Pool pool = _getPool(_dstPoolId);
  324. try pool.redeemLocalCallback(_srcChainId, _srcPoolId, _to, _amountSD, _mintAmountSD) {} catch {
  325. revertLookup[_srcChainId][_srcAddress][_nonce] = abi.encode(
  326. TYPE_REDEEM_LOCAL_CALLBACK_RETRY,
  327. _srcPoolId,
  328. _dstPoolId,
  329. _to,
  330. _amountSD,
  331. _mintAmountSD
  332. );
  333. emit Revert(TYPE_REDEEM_LOCAL_CALLBACK_RETRY, _srcChainId, _srcAddress, _nonce);
  334. }
  335. emit RedeemLocalCallback(_srcChainId, _srcAddress, _nonce, _srcPoolId, _dstPoolId, _to, _amountSD, _mintAmountSD);
  336. }
  337. function swapRemote(
  338. uint16 _srcChainId,
  339. bytes memory _srcAddress,
  340. uint256 _nonce,
  341. uint256 _srcPoolId,
  342. uint256 _dstPoolId,
  343. uint256 _dstGasForCall,
  344. address _to,
  345. Pool.SwapObj memory _s,
  346. bytes memory _payload
  347. ) external onlyBridge {
  348. _swapRemote(_srcChainId, _srcAddress, _nonce, _srcPoolId, _dstPoolId, _dstGasForCall, _to, _s, _payload);
  349. }
  350. function _swapRemote(
  351. uint16 _srcChainId,
  352. bytes memory _srcAddress,
  353. uint256 _nonce,
  354. uint256 _srcPoolId,
  355. uint256 _dstPoolId,
  356. uint256 _dstGasForCall,
  357. address _to,
  358. Pool.SwapObj memory _s,
  359. bytes memory _payload
  360. ) internal {
  361. Pool pool = _getPool(_dstPoolId);
  362. // first try catch the swap remote
  363. try pool.swapRemote(_srcChainId, _srcPoolId, _to, _s) returns (uint256 amountLD) {
  364. if (_payload.length > 0) {
  365. // then try catch the external contract call
  366. try IMirrorgateReceiver(_to).mgReceive{gas: _dstGasForCall}(_srcChainId, _srcAddress, _nonce, pool.token(), amountLD, _payload) {
  367. // do nothing
  368. } catch (bytes memory reason) {
  369. cachedSwapLookup[_srcChainId][_srcAddress][_nonce] = CachedSwap(pool.token(), amountLD, _to, _payload);
  370. emit CachedSwapSaved(_srcChainId, _srcAddress, _nonce, pool.token(), amountLD, _to, _payload, reason);
  371. }
  372. }
  373. } catch {
  374. revertLookup[_srcChainId][_srcAddress][_nonce] = abi.encode(
  375. TYPE_SWAP_REMOTE_RETRY,
  376. _srcPoolId,
  377. _dstPoolId,
  378. _dstGasForCall,
  379. _to,
  380. _s,
  381. _payload
  382. );
  383. emit Revert(TYPE_SWAP_REMOTE_RETRY, _srcChainId, _srcAddress, _nonce);
  384. }
  385. }
  386. //---------------------------------------------------------------------------
  387. // DAO Calls
  388. function createPool(
  389. uint256 _poolId,
  390. address _token,
  391. uint8 _sharedDecimals,
  392. uint8 _localDecimals,
  393. string memory _name,
  394. string memory _symbol
  395. ) external onlyOwner returns (address) {
  396. require(_token != address(0x0), "Mirrorgate: _token cannot be 0x0");
  397. return factory.createPool(_poolId, _token, _sharedDecimals, _localDecimals, _name, _symbol);
  398. }
  399. function createChainPath(
  400. uint256 _poolId,
  401. uint16 _dstChainId,
  402. uint256 _dstPoolId,
  403. uint256 _weight
  404. ) external onlyOwner {
  405. Pool pool = _getPool(_poolId);
  406. pool.createChainPath(_dstChainId, _dstPoolId, _weight);
  407. }
  408. function activateChainPath(
  409. uint256 _poolId,
  410. uint16 _dstChainId,
  411. uint256 _dstPoolId
  412. ) external onlyOwner {
  413. Pool pool = _getPool(_poolId);
  414. pool.activateChainPath(_dstChainId, _dstPoolId);
  415. }
  416. function setWeightForChainPath(
  417. uint256 _poolId,
  418. uint16 _dstChainId,
  419. uint256 _dstPoolId,
  420. uint16 _weight
  421. ) external onlyOwner {
  422. Pool pool = _getPool(_poolId);
  423. pool.setWeightForChainPath(_dstChainId, _dstPoolId, _weight);
  424. }
  425. function setProtocolFeeOwner(address _owner) external onlyOwner {
  426. require(_owner != address(0x0), "Mirrorgate: _owner cannot be 0x0");
  427. protocolFeeOwner = _owner;
  428. }
  429. function setMintFeeOwner(address _owner) external onlyOwner {
  430. require(_owner != address(0x0), "Mirrorgate: _owner cannot be 0x0");
  431. mintFeeOwner = _owner;
  432. }
  433. function setFees(uint256 _poolId, uint256 _mintFeeBP) external onlyOwner {
  434. Pool pool = _getPool(_poolId);
  435. pool.setFee(_mintFeeBP);
  436. }
  437. function setFeeLibrary(uint256 _poolId, address _feeLibraryAddr) external onlyOwner {
  438. Pool pool = _getPool(_poolId);
  439. pool.setFeeLibrary(_feeLibraryAddr);
  440. }
  441. function setSwapStop(uint256 _poolId, bool _swapStop) external onlyOwner {
  442. Pool pool = _getPool(_poolId);
  443. pool.setSwapStop(_swapStop);
  444. }
  445. function setDeltaParam(
  446. uint256 _poolId,
  447. bool _batched,
  448. uint256 _swapDeltaBP,
  449. uint256 _lpDeltaBP,
  450. bool _defaultSwapMode,
  451. bool _defaultLPMode
  452. ) external onlyOwner {
  453. Pool pool = _getPool(_poolId);
  454. pool.setDeltaParam(_batched, _swapDeltaBP, _lpDeltaBP, _defaultSwapMode, _defaultLPMode);
  455. }
  456. function callDelta(uint256 _poolId, bool _fullMode) external {
  457. Pool pool = _getPool(_poolId);
  458. pool.callDelta(_fullMode);
  459. }
  460. function withdrawMintFee(uint256 _poolId, address _to) external {
  461. require(mintFeeOwner == msg.sender, "Mirrorgate: only mintFeeOwner");
  462. Pool pool = _getPool(_poolId);
  463. pool.withdrawMintFeeBalance(_to);
  464. }
  465. function withdrawProtocolFee(uint256 _poolId, address _to) external {
  466. require(protocolFeeOwner == msg.sender, "Mirrorgate: only protocolFeeOwner");
  467. Pool pool = _getPool(_poolId);
  468. pool.withdrawProtocolFeeBalance(_to);
  469. }
  470. }