UNPKG

@ensuro/vaults

Version:

ERC4626s for asset management, used by the Ensuro Protocol

1 lines 13.8 MB
{"id":"126aac7660bdfad2eeca903ba3be3259","_format":"hh-sol-build-info-1","solcVersion":"0.8.28","solcLongVersion":"0.8.28+commit.7893614a","input":{"language":"Solidity","sources":{"@ensuro/swaplibrary/contracts/CurveRoutes.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {ICurveRouter} from \"./dependencies/ICurveRouter.sol\";\nimport {BytesLib} from \"solidity-bytes-utils/contracts/BytesLib.sol\";\n\n/**\n * @title Library to access a set of curve routes stored as tightly packed bytes\n *\n * @dev The format is a concatenation of bytes, packed (ethers.solidityPack in js) with the following fields\n *\n * Fields:\n * <ICurveRouter router>\n * <uint8 numberOfRoutes>\n * -- for each route --\n * <uint8 numberOfSwaps>\n * <address route[i] for i in range((numberOfSwaps * 2) + 1)\n * <uint8 swapParam[i][j] for i in range(numberOfSwaps) for j in range(5)>\n * <address pool[i] for in range(numberOfSwaps)\n * -- end - for each route --\n *\n * @custom:security-contact security@ensuro.co\n * @author Ensuro\n */\nlibrary CurveRoutes {\n using BytesLib for bytes;\n uint256 internal constant ADDRESS_SIZE = 20;\n uint256 internal constant UINT8_SIZE = 1;\n uint256 internal constant MAX_SWAPS = 5;\n uint256 internal constant ROUTER_OFFSET = 0;\n uint256 internal constant N_ROUTES_OFFSET = ROUTER_OFFSET + ADDRESS_SIZE;\n uint256 internal constant ROUTES_BASE_OFFSET = N_ROUTES_OFFSET + UINT8_SIZE;\n\n struct CurveRoute {\n address[11] route;\n /**\n * For each swap array of [i, j, swap type, pool_type, n_coins]\n * See https://github.com/curvefi/curve-router-ng/blob/master/contracts/Router.vy#L514C1-L531C63\n */\n uint256[MAX_SWAPS][5] swapParams;\n address[MAX_SWAPS] pools;\n }\n\n error CurveRouterCantBeZero();\n error AtLeastOneRoute();\n error InvalidLength();\n error InvalidRoute(CurveRoute route);\n error TooManySwaps(uint8 nSwaps);\n error RouteNotFound(address tokenIn, address tokenOut);\n\n function validate(bytes memory curveRoutes) internal pure {\n ICurveRouter router = ICurveRouter(curveRoutes.toAddress(ROUTER_OFFSET));\n if (address(router) == address(0)) revert CurveRouterCantBeZero();\n uint8 nRoutes = curveRoutes.toUint8(N_ROUTES_OFFSET);\n if (nRoutes == 0) revert AtLeastOneRoute();\n uint256 offset = ROUTES_BASE_OFFSET;\n for (uint256 i; i < nRoutes; i++) {\n (uint8 nSwaps, CurveRoute memory route) = readRoute(curveRoutes, offset);\n for (uint256 j; j < nSwaps; j++) {\n if (route.route[j * 2] == address(0) || route.route[j * 2 + 1] == address(0)) revert InvalidRoute(route);\n }\n if (route.route[nSwaps * 2] == address(0)) revert InvalidRoute(route);\n if (nSwaps != MAX_SWAPS && route.route[_routeLen(nSwaps)] != address(0)) revert InvalidRoute(route);\n offset += routeSize(nSwaps);\n }\n if (curveRoutes.length != offset) revert InvalidLength();\n }\n\n function readRoute(\n bytes memory curveRoutes,\n uint256 offset\n ) internal pure returns (uint8 nSwaps, CurveRoute memory route) {\n nSwaps = curveRoutes.toUint8(offset);\n if (nSwaps > MAX_SWAPS) revert TooManySwaps(nSwaps);\n for (uint256 i; i < _routeLen(nSwaps); i++) {\n route.route[i] = curveRoutes.toAddress(offset + UINT8_SIZE + i * ADDRESS_SIZE);\n }\n offset += UINT8_SIZE + _routeLen(nSwaps) * ADDRESS_SIZE;\n for (uint256 i; i < nSwaps; i++) {\n route.swapParams[i][0] = curveRoutes.toUint8(offset + i * UINT8_SIZE * 5);\n route.swapParams[i][1] = curveRoutes.toUint8(offset + i * UINT8_SIZE * 5 + 1);\n route.swapParams[i][2] = curveRoutes.toUint8(offset + i * UINT8_SIZE * 5 + 2);\n route.swapParams[i][3] = curveRoutes.toUint8(offset + i * UINT8_SIZE * 5 + 3);\n route.swapParams[i][4] = curveRoutes.toUint8(offset + i * UINT8_SIZE * 5 + 4);\n }\n offset += nSwaps * UINT8_SIZE * 5;\n for (uint256 i; i < nSwaps; i++) {\n route.pools[i] = curveRoutes.toAddress(offset + i * ADDRESS_SIZE);\n }\n }\n\n function routeSize(uint8 nSwaps) internal pure returns (uint256) {\n return\n UINT8_SIZE + // nSwaps\n _routeLen(nSwaps) *\n ADDRESS_SIZE + // route\n (nSwaps * 5 * UINT8_SIZE) + // swapParams\n (nSwaps * ADDRESS_SIZE); // pools\n }\n\n function _routeLen(uint8 nSwaps) private pure returns (uint256) {\n return (nSwaps * 2 + 1);\n }\n\n function findRoute(\n bytes memory curveRoutes,\n address tokenIn,\n address tokenOut\n ) internal pure returns (ICurveRouter router, CurveRoute memory route) {\n router = ICurveRouter(curveRoutes.toAddress(ROUTER_OFFSET));\n uint8 nRoutes = curveRoutes.toUint8(N_ROUTES_OFFSET);\n uint256 offset = ROUTES_BASE_OFFSET;\n for (uint256 i; i < nRoutes; i++) {\n uint8 nSwaps = curveRoutes.toUint8(offset);\n if (\n curveRoutes.toAddress(offset + UINT8_SIZE) == tokenIn &&\n curveRoutes.toAddress(offset + UINT8_SIZE + ADDRESS_SIZE * nSwaps * 2) == tokenOut\n ) {\n (, route) = readRoute(curveRoutes, offset);\n return (router, route);\n }\n offset += routeSize(nSwaps);\n }\n revert RouteNotFound(tokenIn, tokenOut);\n }\n}\n"},"@ensuro/swaplibrary/contracts/dependencies/ICurveRouter.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.4;\n\n// Generated with cast interface from https://polygonscan.com/address/0xF0d4c12A5768D806021F80a262B4d39d26C58b8D\ninterface ICurveRouter {\n event Exchange(\n address indexed sender,\n address indexed receiver,\n address[11] route,\n uint256[5][5] swap_params,\n address[5] pools,\n uint256 in_amount,\n uint256 out_amount\n );\n\n function exchange(address[11] memory _route, uint256[5][5] memory _swap_params, uint256 _amount, uint256 _expected)\n external\n payable\n returns (uint256);\n function exchange(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _amount,\n uint256 _expected,\n address[5] memory _pools\n ) external payable returns (uint256);\n function exchange(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _amount,\n uint256 _expected,\n address[5] memory _pools,\n address _receiver\n ) external payable returns (uint256);\n function get_dx(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _out_amount,\n address[5] memory _pools\n ) external view returns (uint256);\n function get_dx(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _out_amount,\n address[5] memory _pools,\n address[5] memory _base_pools\n ) external view returns (uint256);\n function get_dx(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _out_amount,\n address[5] memory _pools,\n address[5] memory _base_pools,\n address[5] memory _base_tokens\n ) external view returns (uint256);\n function get_dx(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _out_amount,\n address[5] memory _pools,\n address[5] memory _base_pools,\n address[5] memory _base_tokens,\n address[5] memory _second_base_pools\n ) external view returns (uint256);\n function get_dx(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _out_amount,\n address[5] memory _pools,\n address[5] memory _base_pools,\n address[5] memory _base_tokens,\n address[5] memory _second_base_pools,\n address[5] memory _second_base_tokens\n ) external view returns (uint256);\n function get_dy(address[11] memory _route, uint256[5][5] memory _swap_params, uint256 _amount)\n external\n view\n returns (uint256);\n function get_dy(\n address[11] memory _route,\n uint256[5][5] memory _swap_params,\n uint256 _amount,\n address[5] memory _pools\n ) external view returns (uint256);\n}\n"},"@ensuro/swaplibrary/contracts/interfaces/ISwapRouterErrors.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {ISwapRouter} from \"@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol\";\n\n/**\n * @title ISwapRouterErrors\n *\n */\ninterface ISwapRouterErrors is ISwapRouter {\n error OutputAmountLessThanSlippage(uint256 amountOut, uint256 amountOutMinimum);\n error InputAmountExceedsSlippage(uint256 amountIn, uint256 amountInMaximum);\n error DeadlineInThePast();\n error AmountCannotBeZero();\n error TokenCannotBeZero();\n error RecipientCannotBeZero();\n error NotImplemented();\n}\n"},"@ensuro/swaplibrary/contracts/mocks/SwapRouterMock.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {Math} from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport {SafeCast} from \"@openzeppelin/contracts/utils/math/SafeCast.sol\";\nimport {SafeERC20} from \"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol\";\nimport {ISwapRouter} from \"@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport {ISwapRouterErrors} from \"../interfaces/ISwapRouterErrors.sol\";\n\n/**\n * @title SwapRouterMock\n * @notice SwapRouter mock that can swap a single type of token for several others\n */\ncontract SwapRouterMock is ISwapRouterErrors {\n using SafeERC20 for IERC20Metadata;\n using Math for uint256;\n using SafeCast for uint256;\n\n uint256 internal constant WAD = 1e18;\n\n error AdminCannotBeZero();\n event PriceUpdated(address tokenIn, address tokenOut, uint256 price);\n error NotEnoughBalance(uint256 available, uint256 required);\n\n mapping(address => mapping(address => uint256)) private _prices;\n\n constructor(address admin) {\n require(admin != address(0), AdminCannotBeZero());\n }\n\n function _toWadFactor(address token) internal view returns (uint256) {\n return (10 ** (18 - IERC20Metadata(token).decimals()));\n }\n\n /**\n * @inheritdoc ISwapRouter\n */\n function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut) {\n require(params.recipient != address(0), RecipientCannotBeZero());\n require(params.deadline >= block.timestamp, DeadlineInThePast());\n require(params.amountIn > 0, AmountCannotBeZero());\n\n uint256 amountOutInWad = (params.amountIn * _toWadFactor(params.tokenIn)).mulDiv(\n WAD,\n _prices[params.tokenIn][params.tokenOut]\n );\n amountOut = amountOutInWad / _toWadFactor(params.tokenOut);\n require(amountOut >= params.amountOutMinimum, OutputAmountLessThanSlippage(amountOut, params.amountOutMinimum));\n\n IERC20Metadata(params.tokenIn).safeTransferFrom(msg.sender, address(this), params.amountIn);\n IERC20Metadata(params.tokenOut).safeTransfer(params.recipient, amountOut);\n }\n\n /**\n * @inheritdoc ISwapRouter\n */\n function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn) {\n require(params.recipient != address(0), RecipientCannotBeZero());\n require(params.deadline >= block.timestamp, DeadlineInThePast());\n require(params.amountOut > 0, AmountCannotBeZero());\n uint256 balance = IERC20Metadata(params.tokenOut).balanceOf(address(this));\n require(balance >= params.amountOut, NotEnoughBalance(balance, params.amountOut));\n\n uint256 amountInWad = (params.amountOut * _toWadFactor(params.tokenOut)).mulDiv(\n _prices[params.tokenIn][params.tokenOut],\n WAD\n );\n amountIn = amountInWad / _toWadFactor(params.tokenIn);\n require(amountIn <= params.amountInMaximum, InputAmountExceedsSlippage(amountIn, params.amountInMaximum));\n\n IERC20Metadata(params.tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);\n IERC20Metadata(params.tokenOut).safeTransfer(params.recipient, params.amountOut);\n }\n\n function withdraw(address token, uint256 amount) external {\n require(token != address(0), TokenCannotBeZero());\n require(amount > 0, TokenCannotBeZero());\n IERC20Metadata(token).safeTransfer(msg.sender, amount);\n }\n\n function setCurrentPrice(address tokenIn, address tokenOut, uint256 price_) external {\n require(tokenIn != address(0), TokenCannotBeZero());\n require(tokenOut != address(0), TokenCannotBeZero());\n _prices[tokenIn][tokenOut] = price_;\n emit PriceUpdated(tokenIn, tokenOut, price_);\n }\n\n /**\n * @inheritdoc ISwapRouter\n * @notice This function is not implemented\n */\n function exactOutput(ExactOutputParams calldata) external payable returns (uint256) {\n revert NotImplemented();\n }\n\n /**\n * @inheritdoc ISwapRouter\n * @notice This function is not implemented\n */\n function exactInput(ExactInputParams calldata) external payable returns (uint256) {\n revert NotImplemented();\n }\n\n /**\n * @notice This function is not implemented\n */\n function uniswapV3SwapCallback(int256, int256, bytes calldata) external pure {\n revert NotImplemented();\n }\n}\n"},"@ensuro/swaplibrary/contracts/SwapLibrary.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {ISwapRouter} from \"@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol\";\nimport {ICurveRouter} from \"./dependencies/ICurveRouter.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport {Math} from \"@openzeppelin/contracts/utils/math/Math.sol\";\nimport {CurveRoutes} from \"./CurveRoutes.sol\";\n\n/**\n * @title Swap Library\n * @custom:security-contact security@ensuro.co\n * @author Ensuro\n */\nlibrary SwapLibrary {\n using Math for uint256;\n\n uint256 internal constant WAD = 1e18;\n // Limit on the number of exchanges done by the exactOutput curve workaround\n uint256 internal constant MAX_EXCHANGE = 2;\n\n /**\n * @dev Enum with the different protocols\n */\n enum SwapProtocol {\n undefined,\n uniswap,\n curveRouter\n }\n\n struct SwapConfig {\n SwapProtocol protocol;\n uint256 maxSlippage;\n bytes customParams;\n }\n\n struct UniswapCustomParams {\n uint24 feeTier;\n ISwapRouter router;\n }\n\n error InvalidProtocol();\n error MaxSlippageCannotBeZero();\n error UniswapRouterCannotBeZero();\n error UniswapFeeTierCannotBeZero();\n error AllowanceShouldGoBackToZero();\n error ReceivedLessThanAcceptable(uint256 received, uint256 amountOutMin);\n error SpentMoreThanAcceptable(uint256 spent, uint256 amountInMax);\n\n function validate(SwapConfig calldata swapConfig) external pure {\n if (swapConfig.maxSlippage == 0) revert MaxSlippageCannotBeZero();\n if (swapConfig.protocol == SwapProtocol.uniswap) {\n UniswapCustomParams memory cp = abi.decode(swapConfig.customParams, (UniswapCustomParams));\n if (address(cp.router) == address(0)) revert UniswapRouterCannotBeZero();\n if (cp.feeTier == 0) revert UniswapFeeTierCannotBeZero();\n } else if (swapConfig.protocol == SwapProtocol.curveRouter) {\n CurveRoutes.validate(swapConfig.customParams);\n } else revert InvalidProtocol();\n }\n\n function _toWadFactor(address token) internal view returns (uint256) {\n return (10 ** (18 - IERC20Metadata(token).decimals()));\n }\n\n /**\n * @dev Executes a swap of `amount` from the input token (`tokenIn`) to the output token (`tokenOut`),\n * @param swapConfig Swap configuration including the swap protocol to use.\n * @param tokenIn The address of the token to be swapped.\n * @param tokenOut The address of the token to be received as a result of the swap.\n * @param amount The exact amount of input token to be swapped.\n * @param price Approximate amount of units of tokenIn required to acquire a unit of tokenOut.\n * It will be validated against the swap rate considering the maxSlippage.\n *\n * @notice Should have at least `amount` of tokenIn in the contract to execute the transaction.\n *\n * Requirements:\n * - tokenIn and tokenOut decimals <= 18\n * - SwapConfig must be valid and should be validated using the `validate()` method.\n *\n * @return That exact `amount` went out and an tokenOut amount equal to amount/price +- slippage% came in.\n */\n function exactInput(\n SwapConfig calldata swapConfig,\n address tokenIn,\n address tokenOut,\n uint256 amount,\n uint256 price\n ) external returns (uint256) {\n if (swapConfig.protocol == SwapProtocol.uniswap) {\n return _exactInputUniswap(swapConfig, tokenIn, tokenOut, amount, price);\n } else if (swapConfig.protocol == SwapProtocol.curveRouter) {\n return _exactInputCurve(swapConfig, tokenIn, tokenOut, amount, price);\n }\n return 0;\n }\n\n /**\n * @dev Executes a swap, where the desired output amount of `tokenOut` is specified,\n * @param swapConfig Swap configuration including the protocol to use for the swap.\n * @param tokenIn The address of the token to be used as input for the swap.\n * @param tokenOut The address of the token to be received as a result of the swap.\n * @param amount The desired amount of output tokens (`tokenOut`) to be obtained from the swap.\n * @param price Approximate amount of units of tokenIn required to acquire a unit of tokenOut.\n * It will be validated against the swap rate considering the maxSlippage.\n *\n * @notice Should have sufficient `tokenIn` to fulfill the desired output amount.\n *\n * Requirements:\n * - tokenIn and tokenOut decimals <= 18\n * - SwapConfig must be valid and should be validated using the `validate()` method.\n *\n * @return The actual amount of input tokens (`tokenIn`) spent to obtain the desired output amount (`amount`)\n * should be within the expected slippage range.\n */\n function exactOutput(\n SwapConfig calldata swapConfig,\n address tokenIn,\n address tokenOut,\n uint256 amount,\n uint256 price\n ) external returns (uint256) {\n if (swapConfig.protocol == SwapProtocol.uniswap) {\n return _exactOutputUniswap(swapConfig, tokenIn, tokenOut, amount, price);\n } else if (swapConfig.protocol == SwapProtocol.curveRouter) {\n return _exactOutputCurve(swapConfig, tokenIn, tokenOut, amount, price);\n }\n return 0;\n }\n\n function _calcMinAmount(\n uint256 amount,\n uint256 maxSlippage,\n address tokenIn,\n address tokenOut,\n uint256 price\n ) internal view returns (uint256) {\n return (amount * _toWadFactor(tokenIn)).mulDiv(WAD - maxSlippage, price) / _toWadFactor(tokenOut);\n }\n\n function _calcMaxAmount(\n uint256 amount,\n uint256 maxSlippage,\n address tokenIn,\n address tokenOut,\n uint256 price\n ) internal view returns (uint256) {\n return (amount * _toWadFactor(tokenOut)).mulDiv(price, WAD).mulDiv(WAD + maxSlippage, WAD) / _toWadFactor(tokenIn);\n }\n\n function _exactInputUniswap(\n SwapConfig calldata swapConfig,\n address tokenIn,\n address tokenOut,\n uint256 amount,\n uint256 price\n ) internal returns (uint256) {\n UniswapCustomParams memory cp = abi.decode(swapConfig.customParams, (UniswapCustomParams));\n uint256 amountOutMin = _calcMinAmount(amount, swapConfig.maxSlippage, tokenIn, tokenOut, price);\n\n IERC20Metadata(tokenIn).approve(address(cp.router), amount);\n ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({\n tokenIn: tokenIn,\n tokenOut: tokenOut,\n fee: cp.feeTier,\n recipient: address(this),\n deadline: block.timestamp,\n amountIn: amount,\n amountOutMinimum: amountOutMin,\n sqrtPriceLimitX96: 0 // Since we're limiting the transfer amount, we don't need to worry about the price impact of the transaction\n });\n\n uint256 received = cp.router.exactInputSingle(params);\n if (IERC20Metadata(tokenIn).allowance(address(this), address(cp.router)) != 0) revert AllowanceShouldGoBackToZero();\n // Sanity check\n if (received < amountOutMin) revert ReceivedLessThanAcceptable(received, amountOutMin);\n return received;\n }\n\n function _exactOutputUniswap(\n SwapConfig calldata swapConfig,\n address tokenIn,\n address tokenOut,\n uint256 amount,\n uint256 price\n ) internal returns (uint256) {\n UniswapCustomParams memory cp = abi.decode(swapConfig.customParams, (UniswapCustomParams));\n\n uint256 amountInMax = _calcMaxAmount(amount, swapConfig.maxSlippage, tokenIn, tokenOut, price);\n\n IERC20Metadata(tokenIn).approve(address(cp.router), type(uint256).max);\n ISwapRouter.ExactOutputSingleParams memory params = ISwapRouter.ExactOutputSingleParams({\n tokenIn: tokenIn,\n tokenOut: tokenOut,\n fee: cp.feeTier,\n recipient: address(this),\n deadline: block.timestamp,\n amountOut: amount,\n amountInMaximum: amountInMax,\n sqrtPriceLimitX96: 0 // Since we're limiting the transfer amount, we don't need to worry about the price impact of the transaction\n });\n uint256 actualAmount = cp.router.exactOutputSingle(params);\n\n IERC20Metadata(tokenIn).approve(address(cp.router), 0);\n // Sanity check\n if (actualAmount > amountInMax) revert SpentMoreThanAcceptable(actualAmount, amountInMax);\n return actualAmount;\n }\n\n function _exactInputCurve(\n SwapConfig calldata swapConfig,\n address tokenIn,\n address tokenOut,\n uint256 amount,\n uint256 price\n ) internal returns (uint256 received) {\n (ICurveRouter router, CurveRoutes.CurveRoute memory route) = CurveRoutes.findRoute(\n swapConfig.customParams,\n tokenIn,\n tokenOut\n );\n uint256 amountOutMin = _calcMinAmount(amount, swapConfig.maxSlippage, tokenIn, tokenOut, price);\n\n IERC20Metadata(tokenIn).approve(address(router), amount);\n received = router.exchange(route.route, route.swapParams, amount, amountOutMin, route.pools, address(this));\n\n if (IERC20Metadata(tokenIn).allowance(address(this), address(router)) != 0) revert AllowanceShouldGoBackToZero();\n // Sanity check\n if (received < amountOutMin) revert ReceivedLessThanAcceptable(received, amountOutMin);\n return received;\n }\n\n function _exchangeCurve(\n ICurveRouter router,\n CurveRoutes.CurveRoute memory route,\n uint256 amount\n ) internal returns (uint256 received, uint256 amountInActual) {\n amountInActual = router.get_dx(route.route, route.swapParams, amount, route.pools);\n received = router.exchange(\n route.route,\n route.swapParams,\n amountInActual,\n 0, // I don't verify here, but anyway the token approval defines the limit\n route.pools,\n address(this)\n );\n }\n\n function _exactOutputCurve(\n SwapConfig calldata swapConfig,\n address tokenIn,\n address tokenOut,\n uint256 amount,\n uint256 price\n ) internal returns (uint256) {\n (ICurveRouter router, CurveRoutes.CurveRoute memory route) = CurveRoutes.findRoute(\n swapConfig.customParams,\n tokenIn,\n tokenOut\n );\n uint256 amountInMax = _calcMaxAmount(amount, swapConfig.maxSlippage, tokenIn, tokenOut, price);\n IERC20Metadata(tokenIn).approve(address(router), amountInMax);\n uint256 amountInConsumed = 0;\n\n // Workaround because get_dx isn't reliable - Does up to MAX_EXCHANGE to aproximate as much as possible\n for (uint256 i; amount != 0 && i < MAX_EXCHANGE; i++) {\n (uint256 received, uint256 amountInActual) = _exchangeCurve(router, route, amount);\n amount -= Math.min(amount, received);\n amountInConsumed += amountInActual;\n }\n IERC20Metadata(tokenIn).approve(address(router), 0);\n return amountInConsumed;\n }\n}\n"},"@ensuro/utils/contracts/TestCurrency.sol":{"content":"//SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {AccessControl} from \"@openzeppelin/contracts/access/AccessControl.sol\";\n\ncontract TestCurrency is ERC20, AccessControl {\n bytes32 public constant MINTER_ROLE = keccak256(\"MINTER_ROLE\");\n bytes32 public constant BURNER_ROLE = keccak256(\"BURNER_ROLE\");\n\n uint8 internal immutable _decimals;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint256 initialSupply,\n uint8 decimals_,\n address admin\n ) ERC20(name_, symbol_) {\n _decimals = decimals_;\n _mint(msg.sender, initialSupply);\n _grantRole(DEFAULT_ADMIN_ROLE, admin);\n }\n\n function decimals() public view virtual override returns (uint8) {\n return _decimals;\n }\n\n function mint(address recipient, uint256 amount) external onlyRole(MINTER_ROLE) {\n // require(msg.sender == _owner, \"Only owner can mint\");\n return _mint(recipient, amount);\n }\n\n function burn(address recipient, uint256 amount) external onlyRole(BURNER_ROLE) {\n // require(msg.sender == _owner, \"Only owner can burn\");\n return _burn(recipient, amount);\n }\n}\n"},"@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)\n\npragma solidity ^0.8.20;\n\nimport {IAccessControl} from \"@openzeppelin/contracts/access/IAccessControl.sol\";\nimport {ContextUpgradeable} from \"../utils/ContextUpgradeable.sol\";\nimport {ERC165Upgradeable} from \"../utils/introspection/ERC165Upgradeable.sol\";\nimport {Initializable} from \"../proxy/utils/Initializable.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```solidity\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```solidity\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}\n * to enforce additional security measures for this role.\n */\nabstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControl, ERC165Upgradeable {\n struct RoleData {\n mapping(address account => bool) hasRole;\n bytes32 adminRole;\n }\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n\n /// @custom:storage-location erc7201:openzeppelin.storage.AccessControl\n struct AccessControlStorage {\n mapping(bytes32 role => RoleData) _roles;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.AccessControl\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant AccessControlStorageLocation = 0x02dd7bc7dec4dceedda775e58dd541e08a116c6c53815c0bd028192f7b626800;\n\n function _getAccessControlStorage() private pure returns (AccessControlStorage storage $) {\n assembly {\n $.slot := AccessControlStorageLocation\n }\n }\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with an {AccessControlUnauthorizedAccount} error including the required role.\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role);\n _;\n }\n\n function __AccessControl_init() internal onlyInitializing {\n }\n\n function __AccessControl_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view virtual returns (bool) {\n AccessControlStorage storage $ = _getAccessControlStorage();\n return $._roles[role].hasRole[account];\n }\n\n /**\n * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`\n * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.\n */\n function _checkRole(bytes32 role) internal view virtual {\n _checkRole(role, _msgSender());\n }\n\n /**\n * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`\n * is missing `role`.\n */\n function _checkRole(bytes32 role, address account) internal view virtual {\n if (!hasRole(role, account)) {\n revert AccessControlUnauthorizedAccount(account, role);\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {\n AccessControlStorage storage $ = _getAccessControlStorage();\n return $._roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n *\n * May emit a {RoleGranted} event.\n */\n function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n *\n * May emit a {RoleRevoked} event.\n */\n function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `callerConfirmation`.\n *\n * May emit a {RoleRevoked} event.\n */\n function renounceRole(bytes32 role, address callerConfirmation) public virtual {\n if (callerConfirmation != _msgSender()) {\n revert AccessControlBadConfirmation();\n }\n\n _revokeRole(role, callerConfirmation);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n AccessControlStorage storage $ = _getAccessControlStorage();\n bytes32 previousAdminRole = getRoleAdmin(role);\n $._roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.\n *\n * Internal function without access restriction.\n *\n * May emit a {RoleGranted} event.\n */\n function _grantRole(bytes32 role, address account) internal virtual returns (bool) {\n AccessControlStorage storage $ = _getAccessControlStorage();\n if (!hasRole(role, account)) {\n $._roles[role].hasRole[account] = true;\n emit RoleGranted(role, account, _msgSender());\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.\n *\n * Internal function without access restriction.\n *\n * May emit a {RoleRevoked} event.\n */\n function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {\n AccessControlStorage storage $ = _getAccessControlStorage();\n if (hasRole(role, account)) {\n $._roles[role].hasRole[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n return true;\n } else {\n return false;\n }\n }\n}\n"},"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.20;\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be\n * reused. This mechanism prevents re-execution of each \"step\" but allows the creation of new initialization steps in\n * case an upgrade adds a module that needs to be initialized.\n *\n * For example:\n *\n * [.hljs-theme-light.nopadding]\n * ```solidity\n * contract MyToken is ERC20Upgradeable {\n * function initialize() initializer public {\n * __ERC20_init(\"MyToken\", \"MTK\");\n * }\n * }\n *\n * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {\n * function initializeV2() reinitializer(2) public {\n * __ERC20Permit_init(\"MyToken\");\n * }\n * }\n * ```\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke\n * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() {\n * _disableInitializers();\n * }\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Storage of the initializable contract.\n *\n * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions\n * when using with upgradeable contracts.\n *\n * @custom:storage-location erc7201:openzeppelin.storage.Initializable\n */\n struct InitializableStorage {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n uint64 _initialized;\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool _initializing;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.Initializable\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;\n\n /**\n * @dev The contract is already initialized.\n */\n error InvalidInitialization();\n\n /**\n * @dev The contract is not initializing.\n */\n error NotInitializing();\n\n /**\n * @dev Triggered when the contract has been initialized or reinitialized.\n */\n event Initialized(uint64 version);\n\n /**\n * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,\n * `onlyInitializing` functions can be used to initialize parent contracts.\n *\n * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any\n * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in\n * production.\n *\n * Emits an {Initialized} event.\n */\n modifier initializer() {\n // solhint-disable-next-line var-name-mixedcase\n InitializableStorage storage $ = _getInitializableStorage();\n\n // Cache values to avoid duplicated sloads\n bool isTopLevelCall = !$._initializing;\n uint64 initialized = $._initialized;\n\n // Allowed calls:\n // - initialSetup: the contract is not in the initializing state and no previous version was\n // initialized\n // - construction: the contract is initialized at version 1 (no reininitialization) and the\n // current contract is just being deployed\n bool initialSetup = initialized == 0 && isTopLevelCall;\n bool construction = initialized == 1 && address(this).code.length == 0;\n\n if (!initialSetup && !construction) {\n revert InvalidInitialization();\n }\n $._initialized = 1;\n if (isTopLevelCall) {\n $._initializing = true;\n }\n _;\n if (isTopLevelCall) {\n $._initializing = false;\n emit Initialized(1);\n }\n }\n\n /**\n * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the\n * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be\n * used to initialize parent contracts.\n *\n * A reinitializer may be used after the original initialization step. This is essential to configure modules that\n * are added through upgrades and that require initialization.\n *\n * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`\n * cannot be nested. If one is invoked in the context of another, execution will revert.\n *\n * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in\n * a contract, executing them in the right order is up to the developer or operator.\n *\n * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.\n *\n * Emits an {Initialized} event.\n */\n modifier reinitializer(uint64 version) {\n // solhint-disable-next-line var-name-mixedcase\n InitializableStorage storage $ = _getInitializableStorage();\n\n if ($._initializing || $._initialized >= version) {\n revert InvalidInitialization();\n }\n $._initialized = version;\n $._initializing = true;\n _;\n $._initializing = false;\n emit Initialized(version);\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} and {reinitializer} modifiers, directly or indirectly.\n */\n modifier onlyInitializing() {\n _checkInitializing();\n _;\n }\n\n /**\n * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.\n */\n function _checkInitializing() internal view virtual {\n if (!_isInitializing()) {\n revert NotInitializing();\n }\n }\n\n /**\n * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.\n * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized\n * to any version. It is recommended to use this to lock implementation contracts that are designed to be called\n * through proxies.\n *\n * Emits an {Initialized} event the first time it is successfully executed.\n */\n function _disableInitializers() internal virtual {\n // solhint-disable-next-line var-name-mixedcase\n InitializableStorage storage $ = _getInitializableStorage();\n\n if ($._initializing) {\n revert InvalidInitialization();\n }\n if ($._initialized != type(uint64).max) {\n $._initialized = type(uint64).max;\n emit Initialized(type(uint64).max);\n }\n }\n\n /**\n * @dev Returns the highest version that has been initialized. See {reinitializer}.\n */\n function _getInitializedVersion() internal view returns (uint64) {\n return _getInitializableStorage()._initialized;\n }\n\n /**\n * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.\n */\n function _isInitializing() internal view returns (bool) {\n return _getInitializableStorage()._initializing;\n }\n\n /**\n * @dev Returns a pointer to the storage namespace.\n */\n // solhint-disable-next-line var-name-mixedcase\n function _getInitializableStorage() private pure returns (InitializableStorage storage $) {\n assembly {\n $.slot := INITIALIZABLE_STORAGE\n }\n }\n}\n"},"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.1.0) (proxy/utils/UUPSUpgradeable.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC1822Proxiable} from \"@openzeppelin/contracts/interfaces/draft-IERC1822.sol\";\nimport {ERC1967Utils} from \"@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol\";\nimport {Initializable} from \"./Initializable.sol\";\n\n/**\n * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an\n * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.\n *\n * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is\n * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing\n * `UUPSUpgradeable` with a custom implementation of upgrades.\n *\n * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.\n */\nabstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {\n /// @custom:oz-upgrades-unsafe-allow state-variable-immutable\n address private immutable __self = address(this);\n\n /**\n * @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`\n * and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,\n * while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.\n * If the getter returns `\"5.0.0\"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must\n * be the empty byte string if no function should be called, making it impossible to invoke the `receive` function\n * during an upgrade.\n */\n string public constant UPGRADE_INTERFACE_VERSION = \"5.0.0\";\n\n /**\n * @dev The call is from an unauthorized context.\n */\n error UUPSUnauthorizedCallContext();\n\n /**\n * @dev The storage `slot` is unsupported as a UUID.\n */\n error UUPSUnsupportedProxiableUUID(bytes32 slot);\n\n /**\n * @dev Check that the execution is being performed through a delegatecall call and that the execution context is\n * a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case\n * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a\n * function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to\n * fail.\n */\n modifier onlyProxy() {\n _checkProxy();\n _;\n }\n\n /**\n * @dev Check that the execution is not being performed through a delegate call. This allows a function to be\n * callable on the implementing contract but not through proxies.\n */\n modifier notDelegated() {\n _checkNotDelegated();\n _;\n }\n\n function __UUPSUpgradeable_init() internal onlyInitializing {\n }\n\n function __UUPSUpgradeable_init_unchained() internal onlyInitializing {\n }\n /**\n * @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the\n * implementation. It is used to validate the implementation's compatibility when performing an upgrade.\n *\n * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks\n * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this\n * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\n */\n function proxiableUUID() external view virtual notDelegated returns (bytes32) {\n return ERC1967Utils.IMPLEMENTATION_SLOT;\n }\n\n /**\n * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call\n * encoded in `data`.\n *\n * Calls {_authorizeUpgrade}.\n *\n * Emits an {Upgraded} event.\n *\n * @custom:oz-upgrades-unsafe-allow-reachable delegatecall\n */\n function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {\n _authorizeUpgrade(newImplementation);\n _upgradeToAndCallUUPS(newImplementation, data);\n }\n\n /**\n * @dev Reverts if the execution is not performed via delegatecall or the execution\n * context is not of a proxy with an ERC-1967 compliant implementation pointing to self.\n * See {_onlyProxy}.\n */\n function _checkProxy() internal view virtual {\n if (\n address(this) == __self || // Must be called through delegatecall\n ERC1967Utils.getImplementation() != __self // Must be called through an active proxy\n ) {\n revert UUPSUnauthorizedCallContext();\n }\n }\n\n /**\n * @dev Reverts if the execution is performed via delegatecall.\n * See {notDelegated}.\n */\n function _checkNotDelegated() internal view virtual {\n if (address(this) != __self) {\n // Must not be called through delegatecall\n revert UUPSUnauthorizedCallContext();\n }\n }\n\n /**\n * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by\n * {upgradeToAndCall}.\n *\n * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.\n *\n * ```solidity\n * function _authorizeUpgrade(address) internal onlyOwner {}\n * ```\n */\n function _authorizeUpgrade(address newImplementation) internal virtual;\n\n /**\n * @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.\n *\n * As a security check, {proxiableUUID} is invoked in the new implementation, and the return value\n * is expected to be the implementation slot in ERC-1967.\n *\n * Emits an {IERC1967-Upgraded} event.\n */\n function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {\n try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {\n if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {\n revert UUPSUnsupportedProxiableUUID(slot);\n }\n ERC1967Utils.upgradeToAndCall(newImplementation, data);\n } catch {\n