@ensuro/vaults
Version:
ERC4626s for asset management, used by the Ensuro Protocol
1 lines • 14.6 MB
JSON
{"id":"0f68228b83583b8c45beb00613bcf1ad","_format":"hh-sol-build-info-1","solcVersion":"0.8.30","solcLongVersion":"0.8.30+commit.73712a01","input":{"language":"Solidity","sources":{"@ensuro/access-managed-proxy/contracts/AccessManagedProxy.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {IAccessManager} from \"@openzeppelin/contracts/access/manager/IAccessManager.sol\";\nimport {IAccessManagedProxy} from \"./interfaces/IAccessManagedProxy.sol\";\nimport {AccessManagedProxyBase} from \"./AccessManagedProxyBase.sol\";\nimport {AMPUtils} from \"./AMPUtils.sol\";\n\n/**\n * @title AccessManagedProxy\n * @notice Proxy contract using IAccessManager to manage access control before delegating calls (mutable version)\n * @dev It's a variant of ERC1967Proxy.\n *\n * Currently the check is executed on any call received by the proxy contract (even calls to view methods, i.e.\n * staticcall). For gas efficiency, you can also have `passThruMethods`, and for those methods it will skip\n * the call to the AccessManager, calling directly to the implementation contract.\n *\n * The accessManager and the passThruMethods are in the storage, but this contract doesn't include any method\n * to modify them. They should be modified by the implementation contract (check AMPUtils for functions that\n * encapsulate these operations).\n *\n * Check https://forum.openzeppelin.com/t/accessmanagedproxy-is-a-good-idea/41917 for a discussion on the\n * advantages and disadvantages of using it. Also https://www.youtube.com/watch?v=DKdwJ9Ap9vM for a presentation\n * on this approach.\n *\n * @custom:security-contact security@ensuro.co\n * @author Ensuro\n */\ncontract AccessManagedProxy is AccessManagedProxyBase {\n /// @custom:storage-location erc7201:ensuro.storage.AccessManagedProxy\n /// For struct AMPUtils.AccessManagedProxyStorage\n\n /**\n * @notice Constructor of the proxy, defining the implementation and the access manager\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation` and\n * with `accessManager` as the ACCESS_MANAGER that will handle access control.\n *\n * @param implementation The initial implementation contract.\n * @param _data If nonempty, it's used as data in a delegate call to `implementation`. This will typically be an\n * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.\n * @param accessManager The access manager that will handle access control\n * @param passThruMethods The selector of methods that will skip the access control validation, typically used for\n * views and other methods for gas optimization.\n *\n * @custom:pre If `_data` is empty, `msg.value` must be zero.\n */\n constructor(\n address implementation,\n bytes memory _data,\n IAccessManager accessManager,\n bytes4[] memory passThruMethods\n ) payable AccessManagedProxyBase(implementation, _data) {\n AMPUtils.setAccessManager(accessManager);\n AMPUtils.setPassThruMethods(passThruMethods);\n }\n\n /// @inheritdoc AccessManagedProxyBase\n function _skipAC(bytes4 selector) internal view override returns (bool) {\n return AMPUtils.getAccessManagedProxyStorage().skipAc[selector];\n }\n\n /// @inheritdoc IAccessManagedProxy\n // solhint-disable-next-line func-name-mixedcase\n function PASS_THRU_METHODS() external view override returns (bytes4[] memory methods) {\n return AMPUtils.getAccessManagedProxyStorage().passThruMethods;\n }\n\n /// @inheritdoc IAccessManagedProxy\n // solhint-disable-next-line func-name-mixedcase\n function ACCESS_MANAGER() public view override returns (IAccessManager) {\n return AMPUtils.getAccessManagedProxyStorage().accessManager;\n }\n}\n"},"@ensuro/access-managed-proxy/contracts/AccessManagedProxyBase.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {ERC1967Proxy} from \"@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol\";\nimport {IAccessManager} from \"@openzeppelin/contracts/access/manager/IAccessManager.sol\";\nimport {IAccessManagedProxy} from \"./interfaces/IAccessManagedProxy.sol\";\n\n/**\n * @title AccessManagedProxyBase\n * @notice Proxy contract using IAccessManager to manage access control before delegating calls.\n * @dev It's a variant of ERC1967Proxy.\n *\n * Currently the check is executed on any call received by the proxy contract even calls to view methods\n * (staticcall). In the setup of the ACCESS_MANAGER permissions you would want to make all the views and pure\n * functions enabled for the PUBLIC_ROLE.\n *\n * This base contract delegates on descendent contracts the storage of the ACCESS_MANAGER.\n *\n * Check https://forum.openzeppelin.com/t/accessmanagedproxy-is-a-good-idea/41917 for a discussion on the\n * advantages and disadvantages of using it.\n *\n * @custom:security-contact security@ensuro.co\n * @author Ensuro\n */\nabstract contract AccessManagedProxyBase is ERC1967Proxy, IAccessManagedProxy {\n /**\n * @notice Constructor of the proxy, defining the implementation and the access manager\n * @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation` and\n * with `manager` as the ACCESS_MANAGER that will handle access control.\n *\n * @param implementation The initial implementation contract.\n * @param _data If nonempty, it's used as data in a delegate call to `implementation`. This will typically be an\n * encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.\n *\n * @custom:pre If `_data` is empty, `msg.value` must be zero.\n */\n constructor(address implementation, bytes memory _data) payable ERC1967Proxy(implementation, _data) {}\n\n /// @inheritdoc IAccessManagedProxy\n // solhint-disable-next-line func-name-mixedcase\n function ACCESS_MANAGER() public view virtual returns (IAccessManager);\n\n /// @inheritdoc IAccessManagedProxy\n function authority() external view virtual returns (address) {\n return address(ACCESS_MANAGER());\n }\n\n /**\n * @notice Intercepts the super._delegate call to implement access control\n * @dev Checks with the ACCESS_MANAGER if msg.sender is authorized to call the current call's function,\n * and if so, delegates the current call to `implementation`.\n * @param implementation The implementation contract\n *\n * This function does not return to its internal call site, it will return directly to the external caller.\n */\n function _delegate(address implementation) internal virtual override {\n bytes4 selector = bytes4(msg.data[0:4]);\n bool immediate = _skipAC(selector); // reuse immediate variable both for skipped methods and canCall result\n if (!immediate) {\n (immediate, ) = ACCESS_MANAGER().canCall(msg.sender, address(this), selector);\n if (!immediate) revert AccessManagedUnauthorized(msg.sender);\n }\n super._delegate(implementation);\n }\n\n /**\n * @notice Returns whether to skip the access control validation or not\n * @dev Hook called before ACCESS_MANAGER.canCall to enable skipping the call to the access manager for performance\n * reasons (for example on views) or to remove access control for other specific cases\n * @param selector The selector of the method called\n * @return Whether the access control using ACCESS_MANAGER should be skipped or not\n */\n function _skipAC(bytes4 selector) internal view virtual returns (bool);\n}\n"},"@ensuro/access-managed-proxy/contracts/AMPUtils.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {IAccessManager} from \"@openzeppelin/contracts/access/manager/IAccessManager.sol\";\nimport {IAccessManagedProxy} from \"./interfaces/IAccessManagedProxy.sol\";\n\n/**\n * @title AMPUtils\n * @dev Utility functions for doing custom access control rules, for contracts deployed\n * with AccessManagedProxy\n * @author Ensuro\n */\nlibrary AMPUtils {\n // Error copied from IAccessManaged\n error AccessManagedUnauthorized(address caller);\n\n struct AccessManagedProxyStorage {\n IAccessManager accessManager;\n bytes4[] passThruMethods; // This is used for observability\n mapping(bytes4 => bool) skipAc; // This is the used for actual lookup\n }\n\n /**\n * @notice Storage slot with the address of the current access mananger.\n * @dev Computed as: `keccak256(\n * abi.encode(uint256(keccak256(\"ensuro.storage.AccessManagedProxy\")) - 1)\n * ) & ~bytes32(uint256(0xff))\n */\n // solhint-disable-next-line const-name-snakecase\n bytes32 internal constant AccessManagedProxyStorageLocation =\n 0x787c9d7ac910d64252bcea05acd5b7af6d59644e0451a8bb5674587555049c00;\n\n function getAccessManagedProxyStorage() internal pure returns (AccessManagedProxyStorage storage $) {\n // solhint-disable-next-line no-inline-assembly\n assembly {\n $.slot := AccessManagedProxyStorageLocation\n }\n }\n\n function setAccessManager(IAccessManager accessManager) internal {\n if (address(accessManager).code.length == 0) {\n revert IAccessManagedProxy.AccessManagedInvalidAuthority(address(accessManager));\n }\n getAccessManagedProxyStorage().accessManager = accessManager;\n emit IAccessManagedProxy.AuthorityUpdated(address(accessManager));\n }\n\n function setPassThruMethods(bytes4[] memory passThruMethods) internal {\n AccessManagedProxyStorage storage $ = AMPUtils.getAccessManagedProxyStorage();\n $.passThruMethods = new bytes4[](passThruMethods.length);\n for (uint256 i; i < passThruMethods.length; ++i) {\n $.passThruMethods[i] = passThruMethods[i];\n $.skipAc[passThruMethods[i]] = true;\n }\n emit IAccessManagedProxy.PassThruMethodsChanged(passThruMethods);\n }\n\n function replacePassThruMethods(bytes4[] memory newPassThruMethods) internal {\n AccessManagedProxyStorage storage $ = AMPUtils.getAccessManagedProxyStorage();\n bytes4[] memory oldPassThruMethods = $.passThruMethods;\n for (uint256 i; i < oldPassThruMethods.length; ++i) {\n $.skipAc[oldPassThruMethods[i]] = false;\n }\n setPassThruMethods(newPassThruMethods);\n }\n\n /**\n * @dev Checks if the user can call a particular selector, assuming the calling contract was deployed as an AMP.\n *\n * @param user The user for which you want to check the access, typically msg.sender\n * @param selector The selector of the method called (or a fake selector generated with makeSelector or another way)\n */\n function checkCanCall(address user, bytes4 selector) internal view {\n (bool immediate, ) = IAccessManagedProxy(payable(address(this))).ACCESS_MANAGER().canCall(\n user,\n address(this),\n selector\n );\n require(immediate, AccessManagedUnauthorized(user));\n }\n\n /**\n * @dev Standard way of creating \"fake selectors\" (not necessarily tied to a method call) for specific permissions\n */\n function makeSelector(bytes memory something) internal pure returns (bytes4) {\n return bytes4(keccak256(something));\n }\n}\n"},"@ensuro/access-managed-proxy/contracts/interfaces/IAccessManagedProxy.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.28;\n\nimport {IAccessManager} from \"@openzeppelin/contracts/access/manager/IAccessManager.sol\";\n\n/**\n * @title IAccessManagedProxy - Interface of AccessManagedProxy contracts\n * @notice This interface gives observability of the access control setup\n *\n * @dev The `ACCESS_MANAGER()` is the AccessManager contract that stores the access roles for most of the methods,\n * except those listed in `PASS_THRU_METHODS()` that are forwarded directly to the proxy and don't have access control\n * (at least not by the AccessManager contract).\n *\n * @author Ensuro\n */\ninterface IAccessManagedProxy {\n /**\n * @notice The ACCESS_MANAGER that manages the access controls was updated\n * @dev Authority that manages this contract was updated. Uses same interface as OZ's IAccessManaged\n */\n // solhint-disable-next-line gas-indexed-events\n event AuthorityUpdated(address authority);\n\n /**\n * @dev Emitted when the passThruMethods has changed.\n */\n event PassThruMethodsChanged(bytes4[] newPassThruMethods);\n\n // Errors copied from OZ's IAccessManaged\n error AccessManagedUnauthorized(address caller);\n error AccessManagedInvalidAuthority(address authority);\n\n /**\n * @notice Returns the current authority.\n * @dev Returns the current authority. Same as ACCESS_MANAGER(), added for compatibility with OZ's IAccessManaged\n */\n function authority() external view returns (address);\n\n /**\n * @notice AccessManager contract that handles the permissions to access the implementation methods\n */\n // solhint-disable-next-line func-name-mixedcase\n function ACCESS_MANAGER() external view returns (IAccessManager);\n\n /**\n * @notice Gives observability to the methods that are skipped from access control\n * @return methods The list of method selectors that skip ACCESS_MANAGER access control\n */\n // solhint-disable-next-line func-name-mixedcase\n function PASS_THRU_METHODS() external view returns (bytes4[] memory methods);\n}\n"},"@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\";\n\ncontract TestCurrency is ERC20 {\n uint8 internal immutable _decimals;\n\n constructor(\n string memory name_,\n string memory symbol_,\n uint256 initialSupply,\n uint8 decimals_\n ) ERC20(name_, symbol_) {\n _decimals = decimals_;\n _mint(msg.sender, initialSupply);\n }\n\n function decimals() public view virtual override returns (uint8) {\n return _decimals;\n }\n\n function mint(address recipient, uint256 amount) public virtual {\n return _mint(recipient, amount);\n }\n\n function burn(address recipient, uint256 amount) public virtual {\n return _burn(recipient, amount);\n }\n}\n"},"@ensuro/utils/contracts/TestERC4626.sol":{"content":"//SPDX-License-Identifier: Apache-2.0\npragma solidity ^0.8.0;\n\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ERC4626} from \"@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\n\ninterface IMintable {\n function mint(address recipient, uint256 amount) external;\n function burn(address recipient, uint256 amount) external;\n}\n\ncontract TestERC4626 is ERC4626 {\n bool internal _broken;\n\n uint256 public overrideMaxDeposit;\n uint256 public overrideMaxMint;\n uint256 public overrideMaxWithdraw;\n uint256 public overrideMaxRedeem;\n\n uint256 public constant OVERRIDE_UNSET = type(uint256).max - 99;\n\n enum OverrideOption {\n deposit,\n mint,\n withdraw,\n redeem\n }\n\n error VaultIsBroken(bytes4 selector);\n\n modifier isBroken() {\n require(!_broken, VaultIsBroken(bytes4(msg.data[0:4])));\n _;\n }\n\n constructor(string memory name_, string memory symbol_, IERC20Metadata asset_) ERC20(name_, symbol_) ERC4626(asset_) {\n overrideMaxRedeem = overrideMaxWithdraw = overrideMaxMint = overrideMaxDeposit = OVERRIDE_UNSET;\n }\n\n function _deposit(\n address caller,\n address receiver,\n uint256 assets,\n uint256 shares\n ) internal virtual override isBroken {\n super._deposit(caller, receiver, assets, shares);\n }\n\n function _withdraw(\n address caller,\n address receiver,\n address owner,\n uint256 assets,\n uint256 shares\n ) internal virtual override isBroken {\n super._withdraw(caller, receiver, owner, assets, shares);\n }\n\n /*\n * @dev Adds or remove assets not generated by deposits/withdraw - For testing discrete earnings/losses\n */\n function discreteEarning(int256 assets) external {\n if (assets > 0) {\n IMintable(asset()).mint(address(this), uint256(assets));\n } else {\n IMintable(asset()).burn(address(this), uint256(-assets));\n }\n }\n\n function setBroken(bool broken_) external {\n _broken = broken_;\n }\n\n function broken() external view returns (bool) {\n return _broken;\n }\n\n function maxDeposit(address owner) public view override returns (uint256) {\n return overrideMaxDeposit == OVERRIDE_UNSET ? super.maxDeposit(owner) : overrideMaxDeposit;\n }\n\n function maxMint(address owner) public view override returns (uint256) {\n return overrideMaxMint == OVERRIDE_UNSET ? super.maxMint(owner) : overrideMaxMint;\n }\n\n function maxWithdraw(address owner) public view override returns (uint256) {\n return overrideMaxWithdraw == OVERRIDE_UNSET ? super.maxWithdraw(owner) : overrideMaxWithdraw;\n }\n\n function maxRedeem(address owner) public view override returns (uint256) {\n return overrideMaxRedeem == OVERRIDE_UNSET ? super.maxRedeem(owner) : overrideMaxRedeem;\n }\n\n function setOverride(OverrideOption option, uint256 newValue) external {\n if (option == OverrideOption.deposit) overrideMaxDeposit = newValue;\n if (option == OverrideOption.mint) overrideMaxMint = newValue;\n if (option == OverrideOption.withdraw) overrideMaxWithdraw = newValue;\n if (option == OverrideOption.redeem) overrideMaxRedeem = newValue;\n }\n}\n"},"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.22;\n\nimport {UUPSUpgradeable} from \"@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol\";\n"},"@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v5.5.0) (token/ERC20/ERC20.sol)\n\npragma solidity ^0.8.20;\n\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {IERC20Metadata} from \"@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol\";\nimport {ContextUpgradeable} from \"../../utils/ContextUpgradeable.sol\";\nimport {IERC20Errors} from \"@openzeppelin/contracts/interfaces/draft-IERC6093.sol\";\nimport {Initializable} from \"@openzeppelin/contracts/proxy/utils/Initializable.sol\";\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * The default value of {decimals} is 18. To change this, you should override\n * this function so it returns a different value.\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC-20\n * applications.\n */\nabstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {\n /// @custom:storage-location erc7201:openzeppelin.storage.ERC20\n struct ERC20Storage {\n mapping(address account => uint256) _balances;\n\n mapping(address account => mapping(address spender => uint256)) _allowances;\n\n uint256 _totalSupply;\n\n string _name;\n string _symbol;\n }\n\n // keccak256(abi.encode(uint256(keccak256(\"openzeppelin.storage.ERC20\")) - 1)) & ~bytes32(uint256(0xff))\n bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00;\n\n function _getERC20Storage() private pure returns (ERC20Storage storage $) {\n assembly {\n $.slot := ERC20StorageLocation\n }\n }\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * Both values are immutable: they can only be set once during construction.\n */\n function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {\n __ERC20_init_unchained(name_, symbol_);\n }\n\n function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {\n ERC20Storage storage $ = _getERC20Storage();\n $._name = name_;\n $._symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual returns (string memory) {\n ERC20Storage storage $ = _getERC20Storage();\n return $._name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual returns (string memory) {\n ERC20Storage storage $ = _getERC20Storage();\n return $._symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the default value returned by this function, unless\n * it's overridden.\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual returns (uint8) {\n return 18;\n }\n\n /// @inheritdoc IERC20\n function totalSupply() public view virtual returns (uint256) {\n ERC20Storage storage $ = _getERC20Storage();\n return $._totalSupply;\n }\n\n /// @inheritdoc IERC20\n function balanceOf(address account) public view virtual returns (uint256) {\n ERC20Storage storage $ = _getERC20Storage();\n return $._balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `value`.\n */\n function transfer(address to, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, value);\n return true;\n }\n\n /// @inheritdoc IERC20\n function allowance(address owner, address spender) public view virtual returns (uint256) {\n ERC20Storage storage $ = _getERC20Storage();\n return $._allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 value) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, value);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Skips emitting an {Approval} event indicating an allowance update. This is not\n * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `value`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `value`.\n */\n function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, value);\n _transfer(from, to, value);\n return true;\n }\n\n /**\n * @dev Moves a `value` amount of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * NOTE: This function is not virtual, {_update} should be overridden instead.\n */\n function _transfer(address from, address to, uint256 value) internal {\n if (from == address(0)) {\n revert ERC20InvalidSender(address(0));\n }\n if (to == address(0)) {\n revert ERC20InvalidReceiver(address(0));\n }\n _update(from, to, value);\n }\n\n /**\n * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`\n * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding\n * this function.\n *\n * Emits a {Transfer} event.\n */\n function _update(address from, address to, uint256 value) internal virtual {\n ERC20Storage storage $ = _getERC20Storage();\n if (from == address(0)) {\n // Overflow check required: The rest of the code assumes that totalSupply never overflows\n $._totalSupply += value;\n } else {\n uint256 fromBalance = $._balances[from];\n if (fromBalance < value) {\n revert ERC20InsufficientBalance(from, fromBalance, value);\n }\n unchecked {\n // Overflow not possible: value <= fromBalance <= totalSupply.\n $._balances[from] = fromBalance - value;\n }\n }\n\n if (to == a