// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (access/AccessControl.sol)pragmasolidity ^0.8.20;import {IAccessControl} from"./IAccessControl.sol";
import {Context} from"../utils/Context.sol";
import {ERC165} from"../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/abstractcontractAccessControlisContext, IAccessControl, ERC165{
structRoleData {
mapping(address account =>bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32publicconstant DEFAULT_ADMIN_ROLE =0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/modifieronlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualoverridereturns (bool) {
return interfaceId ==type(IAccessControl).interfaceId||super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/functionhasRole(bytes32 role, address account) publicviewvirtualreturns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/function_checkRole(bytes32 role) internalviewvirtual{
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/function_checkRole(bytes32 role, address account) internalviewvirtual{
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/functiongetRoleAdmin(bytes32 role) publicviewvirtualreturns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/functiongrantRole(bytes32 role, address account) publicvirtualonlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/functionrevokeRole(bytes32 role, address account) publicvirtualonlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/functionrenounceRole(bytes32 role, address callerConfirmation) publicvirtual{
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/function_setRoleAdmin(bytes32 role, bytes32 adminRole) internalvirtual{
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/function_grantRole(bytes32 role, address account) internalvirtualreturns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] =true;
emit RoleGranted(role, account, _msgSender());
returntrue;
} else {
returnfalse;
}
}
/**
* @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/function_revokeRole(bytes32 role, address account) internalvirtualreturns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] =false;
emit RoleRevoked(role, account, _msgSender());
returntrue;
} else {
returnfalse;
}
}
}
Contract Source Code
File 2 of 24: Address.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)pragmasolidity ^0.8.20;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/errorAddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/errorAddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/errorFailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/functionsendValue(addresspayable recipient, uint256 amount) internal{
if (address(this).balance< amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/functionfunctionCallWithValue(address target, bytesmemory data, uint256 value) internalreturns (bytesmemory) {
if (address(this).balance< value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytesmemory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
(bool success, bytesmemory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/functionverifyCallResultFromTarget(address target,
bool success,
bytesmemory returndata
) internalviewreturns (bytesmemory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty// otherwise we already know that it was a contractif (returndata.length==0&& target.code.length==0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/functionverifyCallResult(bool success, bytesmemory returndata) internalpurereturns (bytesmemory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/function_revert(bytesmemory returndata) privatepure{
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assembly/// @solidity memory-safe-assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
Contract Source Code
File 3 of 24: Common.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"v3-periphery/interfaces/external/IWETH9.sol";
import"v3-periphery/interfaces/INonfungiblePositionManager.sol"asuniv3;
import"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import"v3-core/libraries/FullMath.sol";
import"@openzeppelin/contracts/access/AccessControl.sol";
import"@openzeppelin/contracts/utils/Pausable.sol";
interfaceINonfungiblePositionManagerisuniv3.INonfungiblePositionManager{
/// @notice mintParams for algebra v1structAlgebraV1MintParams {
address token0;
address token1;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
/// @notice Creates a new position wrapped in a NFT/// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized/// a method does not exist, i.e. the pool is assumed to be initialized./// @param params The params necessary to mint a position, encoded as `MintParams` in calldata/// @return tokenId The ID of the token that represents the minted position/// @return liquidity The amount of liquidity for this position/// @return amount0 The amount of token0/// @return amount1 The amount of token1functionmint(AlgebraV1MintParams calldata params)
externalpayablereturns (uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
/// @return Returns the address of WNativeTokenfunctionWNativeToken() externalviewreturns (address);
}
abstractcontractCommonisAccessControl, Pausable{
bytes32publicconstant WITHDRAWER_ROLE =keccak256("WITHDRAWER_ROLE");
bytes32publicconstant ADMIN_ROLE =keccak256("ADMIN_ROLE");
uint256internalconstant Q64 =2**64;
uint256internalconstant Q96 =2**96;
// error typeserrorSelfSend();
errorNotSupportedAction();
errorNotSupportedProtocol();
errorSameToken();
errorAmountError();
errorSlippageError();
errorCollectError();
errorTransferError();
errorEtherSendFailed();
errorTooMuchEtherSent();
errorNoEtherToken();
errorNotWETH();
errorTooMuchFee();
errorAlreadyInitialised();
errorGetPositionFailed();
errorNoFees();
structDeductFeesEventData {
address token0;
address token1;
address token2;
uint256 amount0;
uint256 amount1;
uint256 amount2;
uint256 feeAmount0;
uint256 feeAmount1;
uint256 feeAmount2;
uint64 feeX64;
FeeType feeType;
}
// eventseventCompoundFees(addressindexed nfpm, uint256indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
eventDeductFees(addressindexed nfpm, uint256indexed tokenId, addressindexed userAddress, DeductFeesEventData data);
eventChangeRange(addressindexed nfpm, uint256indexed tokenId, uint256 newTokenId, uint256 newLiquidity, uint256 token0Added, uint256 token1Added);
eventWithdrawAndCollectAndSwap(addressindexed nfpm, uint256indexed tokenId, address token, uint256 amount);
eventSwap(addressindexed tokenIn, addressindexed tokenOut, uint256 amountIn, uint256 amountOut);
eventSwapAndMint(addressindexed nfpm, uint256indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
eventSwapAndIncreaseLiquidity(addressindexed nfpm, uint256indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
addresspublic swapRouter;
mapping (FeeType=>uint64) _maxFeeX64;
constructor() {
_maxFeeX64[FeeType.GAS_FEE] =1844674407370955264; // 10%
_maxFeeX64[FeeType.PROTOCOL_FEE] =1844674407370955264; // 10%
}
boolprivate _initialized =false;
functioninitialize(address router, address admin, address withdrawer) publicvirtual{
if (_initialized) {
revert AlreadyInitialised();
}
if (withdrawer ==address(0)) {
revert();
}
_grantRole(ADMIN_ROLE, admin);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(WITHDRAWER_ROLE, withdrawer);
_grantRole(DEFAULT_ADMIN_ROLE, withdrawer);
swapRouter = router;
_initialized =true;
}
/// @notice protocol to provide lpenumProtocol {
UNI_V3,
ALGEBRA_V1
}
enumFeeType {
GAS_FEE,
PROTOCOL_FEE
// todo: PERFORMANCE_FEE
}
/// @notice Params for swapAndMint() functionstructSwapAndMintParams {
Protocol protocol;
INonfungiblePositionManager nfpm;
IERC20 token0;
IERC20 token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint64 protocolFeeX64;
// how much is provided of token0 and token1uint256 amount0;
uint256 amount1;
uint256 amount2;
address recipient; // recipient of tokensuint256 deadline;
// source token for swaps (maybe either address(0), token0, token1 or another token)// if swapSourceToken is another token than token0 or token1 -> amountIn0 + amountIn1 of swapSourceToken are expected to be available
IERC20 swapSourceToken;
// if swapSourceToken needs to be swapped to token0 - set valuesuint256 amountIn0;
uint256 amountOut0Min;
bytes swapData0;
// if swapSourceToken needs to be swapped to token1 - set valuesuint256 amountIn1;
uint256 amountOut1Min;
bytes swapData1;
// min amount to be added after swapuint256 amountAddMin0;
uint256 amountAddMin1;
}
/// @notice Params for swapAndIncreaseLiquidity() functionstructSwapAndIncreaseLiquidityParams {
Protocol protocol;
INonfungiblePositionManager nfpm;
uint256 tokenId;
// how much is provided of token0 and token1uint256 amount0;
uint256 amount1;
uint256 amount2;
address recipient; // recipient of leftover tokensuint256 deadline;
// source token for swaps (maybe either address(0), token0, token1 or another token)// if swapSourceToken is another token than token0 or token1 -> amountIn0 + amountIn1 of swapSourceToken are expected to be available
IERC20 swapSourceToken;
// if swapSourceToken needs to be swapped to token0 - set valuesuint256 amountIn0;
uint256 amountOut0Min;
bytes swapData0;
// if swapSourceToken needs to be swapped to token1 - set valuesuint256 amountIn1;
uint256 amountOut1Min;
bytes swapData1;
// min amount to be added after swapuint256 amountAddMin0;
uint256 amountAddMin1;
uint64 protocolFeeX64;
}
structReturnLeftoverTokensParams{
IWETH9 weth;
address to;
IERC20 token0;
IERC20 token1;
uint256 total0;
uint256 total1;
uint256 added0;
uint256 added1;
bool unwrap;
}
structDecreaseAndCollectFeesParams {
INonfungiblePositionManager nfpm;
address userAddress;
IERC20 token0;
IERC20 token1;
uint256 tokenId;
uint128 liquidity;
uint256 deadline;
uint256 token0Min;
uint256 token1Min;
bool compoundFees;
}
structDeductFeesParams {
uint256 amount0;
uint256 amount1;
uint256 amount2;
uint64 feeX64;
FeeType feeType;
// readonly params for emitting eventsaddress nfpm;
uint256 tokenId;
address userAddress;
address token0;
address token1;
address token2;
}
/**
* @notice Withdraws erc20 token balance
* @param tokens Addresses of erc20 tokens to withdraw
* @param to Address to send to
*/functionwithdrawERC20(IERC20[] calldata tokens, address to) externalonlyRole(WITHDRAWER_ROLE) {
uint count = tokens.length;
for(uint i =0; i < count; ++i) {
uint256 balance = tokens[i].balanceOf(address(this));
if (balance >0) {
SafeERC20.safeTransfer(tokens[i], to, balance);
}
}
}
/**
* @notice Withdraws native token balance
* @param to Address to send to
*/functionwithdrawNative(address to) externalonlyRole(WITHDRAWER_ROLE) {
uint256 nativeBalance =address(this).balance;
if (nativeBalance >0) {
payable(to).transfer(nativeBalance);
}
}
/**
* @notice Withdraws erc721 token balance
* @param nfpm Addresses of erc721 tokens to withdraw
* @param tokenId tokenId of erc721 tokens to withdraw
* @param to Address to send to
*/functionwithdrawERC721(INonfungiblePositionManager nfpm, uint256 tokenId, address to) externalonlyRole(WITHDRAWER_ROLE) {
nfpm.transferFrom(address(this), to, tokenId);
}
// checks if required amounts are provided and are exact - wraps any provided ETH as WETH// if less or more provided revertsfunction_prepareSwap(IWETH9 weth, IERC20 token0, IERC20 token1, IERC20 otherToken, uint256 amount0, uint256 amount1, uint256 amountOther) internal{
uint256 amountAdded0;
uint256 amountAdded1;
uint256 amountAddedOther;
// wrap ether sentif (msg.value!=0) {
weth.deposit{ value: msg.value }();
if (address(weth) ==address(token0)) {
amountAdded0 =msg.value;
if (amountAdded0 > amount0) {
revert TooMuchEtherSent();
}
} elseif (address(weth) ==address(token1)) {
amountAdded1 =msg.value;
if (amountAdded1 > amount1) {
revert TooMuchEtherSent();
}
} elseif (address(weth) ==address(otherToken)) {
amountAddedOther =msg.value;
if (amountAddedOther > amountOther) {
revert TooMuchEtherSent();
}
} else {
revert NoEtherToken();
}
}
// get missing tokens (fails if not enough provided)if (amount0 > amountAdded0) {
uint256 balanceBefore = token0.balanceOf(address(this));
SafeERC20.safeTransferFrom(token0, msg.sender, address(this), amount0 - amountAdded0);
uint256 balanceAfter = token0.balanceOf(address(this));
if (balanceAfter - balanceBefore != amount0 - amountAdded0) {
revert TransferError(); // reverts for fee-on-transfer tokens
}
}
if (amount1 > amountAdded1) {
uint256 balanceBefore = token1.balanceOf(address(this));
SafeERC20.safeTransferFrom(token1, msg.sender, address(this), amount1 - amountAdded1);
uint256 balanceAfter = token1.balanceOf(address(this));
if (balanceAfter - balanceBefore != amount1 - amountAdded1) {
revert TransferError(); // reverts for fee-on-transfer tokens
}
}
if (amountOther > amountAddedOther &&address(otherToken) !=address(0) && token0 != otherToken && token1 != otherToken) {
uint256 balanceBefore = otherToken.balanceOf(address(this));
SafeERC20.safeTransferFrom(otherToken, msg.sender, address(this), amountOther - amountAddedOther);
uint256 balanceAfter = otherToken.balanceOf(address(this));
if (balanceAfter - balanceBefore != amountOther - amountAddedOther) {
revert TransferError(); // reverts for fee-on-transfer tokens
}
}
}
structSwapAndMintResult {
uint256 tokenId;
uint128 liquidity;
uint256 added0;
uint256 added1;
}
// swap and mint logicfunction_swapAndMint(SwapAndMintParams memory params, bool unwrap) internalreturns (SwapAndMintResult memory result) {
(uint256 total0, uint256 total1) = _swapAndPrepareAmounts(params, unwrap);
if (params.protocol == Protocol.UNI_V3) {
// mint is done to address(this) because it is not a safemint and safeTransferFrom needs to be done manually afterwards
(result.tokenId,result.liquidity,result.added0,result.added1) = _mintUniv3(params.nfpm, univ3.INonfungiblePositionManager.MintParams(
address(params.token0),
address(params.token1),
params.fee,
params.tickLower,
params.tickUpper,
total0,
total1,
params.amountAddMin0,
params.amountAddMin1,
address(this), // is sent to real recipient aftwards
params.deadline
));
} elseif (params.protocol == Protocol.ALGEBRA_V1) {
// mint is done to address(this) because it is not a safemint and safeTransferFrom needs to be done manually afterwards
(result.tokenId,result.liquidity,result.added0,result.added1) = _mintAlgebraV1(params.nfpm, univ3.INonfungiblePositionManager.MintParams(
address(params.token0),
address(params.token1),
params.fee,
params.tickLower,
params.tickUpper,
total0,
total1,
params.amountAddMin0,
params.amountAddMin1,
address(this), // is sent to real recipient aftwards
params.deadline
));
} else {
revert NotSupportedProtocol();
}
params.nfpm.transferFrom(address(this), params.recipient, result.tokenId);
emit SwapAndMint(address(params.nfpm), result.tokenId, result.liquidity, result.added0, result.added1);
_returnLeftoverTokens(ReturnLeftoverTokensParams(_getWeth9(params.nfpm, params.protocol), params.recipient, params.token0, params.token1, total0, total1, result.added0, result.added1, unwrap));
}
function_mintUniv3(INonfungiblePositionManager nfpm, INonfungiblePositionManager.MintParams memory params) internalreturns (uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
) {
// mint is done to address(this) because it is not a safemint and safeTransferFrom needs to be done manually afterwardsreturn nfpm.mint(params);
}
function_mintAlgebraV1(INonfungiblePositionManager nfpm, INonfungiblePositionManager.MintParams memory params) internalreturns (uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
) {
INonfungiblePositionManager.AlgebraV1MintParams memory mintParams =
INonfungiblePositionManager.AlgebraV1MintParams(
params.token0,
params.token1,
params.tickLower,
params.tickUpper,
params.amount0Desired,
params.amount1Desired,
params.amount0Min,
params.amount1Min,
address(this), // is sent to real recipient aftwards
params.deadline
);
// mint is done to address(this) because it is not a safemint and safeTransferFrom needs to be done manually afterwardsreturn nfpm.mint(mintParams);
}
structSwapAndIncreaseLiquidityResult {
uint128 liquidity;
uint256 added0;
uint256 added1;
uint256 feeAmount0;
uint256 feeAmount1;
}
// swap and increase logicfunction_swapAndIncrease(SwapAndIncreaseLiquidityParams memory params, IERC20 token0, IERC20 token1, bool unwrap) internalreturns (SwapAndIncreaseLiquidityResult memory result) {
(uint256 total0, uint256 total1) = _swapAndPrepareAmounts(
SwapAndMintParams(params.protocol, params.nfpm, token0, token1, 0, 0, 0, 0, params.amount0, params.amount1, 0, params.recipient, params.deadline, params.swapSourceToken, params.amountIn0, params.amountOut0Min, params.swapData0, params.amountIn1, params.amountOut1Min, params.swapData1, params.amountAddMin0, params.amountAddMin1), unwrap);
INonfungiblePositionManager.IncreaseLiquidityParams memory increaseLiquidityParams =
univ3.INonfungiblePositionManager.IncreaseLiquidityParams(
params.tokenId,
total0,
total1,
params.amountAddMin0,
params.amountAddMin1,
params.deadline
);
(result.liquidity, result.added0, result.added1) = params.nfpm.increaseLiquidity(increaseLiquidityParams);
emit SwapAndIncreaseLiquidity(address(params.nfpm), params.tokenId, result.liquidity, result.added0, result.added1);
IWETH9 weth = _getWeth9(params.nfpm, params.protocol);
_returnLeftoverTokens(ReturnLeftoverTokensParams(weth, params.recipient, token0, token1, total0, total1, result.added0, result.added1, unwrap));
}
// swaps available tokens and prepares max amounts to be added to nfpmfunction_swapAndPrepareAmounts(SwapAndMintParams memory params, bool unwrap) internalreturns (uint256 total0, uint256 total1) {
if (params.swapSourceToken == params.token0) {
if (params.amount0 < params.amountIn1) {
revert AmountError();
}
(uint256 amountInDelta, uint256 amountOutDelta) = _swap(params.token0, params.token1, params.amountIn1, params.amountOut1Min, params.swapData1);
total0 = params.amount0 - amountInDelta;
total1 = params.amount1 + amountOutDelta;
} elseif (params.swapSourceToken == params.token1) {
if (params.amount1 < params.amountIn0) {
revert AmountError();
}
(uint256 amountInDelta, uint256 amountOutDelta) = _swap(params.token1, params.token0, params.amountIn0, params.amountOut0Min, params.swapData0);
total1 = params.amount1 - amountInDelta;
total0 = params.amount0 + amountOutDelta;
} elseif (address(params.swapSourceToken) !=address(0)) {
(uint256 amountInDelta0, uint256 amountOutDelta0) = _swap(params.swapSourceToken, params.token0, params.amountIn0, params.amountOut0Min, params.swapData0);
(uint256 amountInDelta1, uint256 amountOutDelta1) = _swap(params.swapSourceToken, params.token1, params.amountIn1, params.amountOut1Min, params.swapData1);
total0 = params.amount0 + amountOutDelta0;
total1 = params.amount1 + amountOutDelta1;
// return third token leftover if anyuint256 leftOver = params.amountIn0 + params.amountIn1 - amountInDelta0 - amountInDelta1;
if (leftOver !=0) {
IWETH9 weth = _getWeth9(params.nfpm, params.protocol);
_transferToken(weth, params.recipient, params.swapSourceToken, leftOver, unwrap);
}
} else {
total0 = params.amount0;
total1 = params.amount1;
}
if (total0 !=0) {
params.token0.approve(address(params.nfpm), total0);
}
if (total1 !=0) {
params.token1.approve(address(params.nfpm), total1);
}
}
// returns leftover token balancesfunction_returnLeftoverTokens(ReturnLeftoverTokensParams memory params) internal{
uint256 left0 = params.total0 - params.added0;
uint256 left1 = params.total1 - params.added1;
// return leftoversif (left0 !=0) {
_transferToken(params.weth, params.to, params.token0, left0, params.unwrap);
}
if (left1 !=0) {
_transferToken(params.weth, params.to, params.token1, left1, params.unwrap);
}
}
// transfers token (or unwraps WETH and sends ETH)function_transferToken(IWETH9 weth, address to, IERC20 token, uint256 amount, bool unwrap) internal{
if (address(weth) ==address(token) && unwrap) {
weth.withdraw(amount);
(bool sent, ) = to.call{value: amount}("");
if (!sent) {
revert EtherSendFailed();
}
} else {
SafeERC20.safeTransfer(token, to, amount);
}
}
// general swap function which uses external router with off-chain calculated swap instructions// does slippage check with amountOutMin param// returns token amounts deltas after swapfunction_swap(IERC20 tokenIn, IERC20 tokenOut, uint256 amountIn, uint256 amountOutMin, bytesmemory swapData) internalreturns (uint256 amountInDelta, uint256 amountOutDelta) {
if (amountIn !=0&& swapData.length!=0&&address(tokenOut) !=address(0)) {
uint256 balanceInBefore = tokenIn.balanceOf(address(this));
uint256 balanceOutBefore = tokenOut.balanceOf(address(this));
// approve needed amount
tokenIn.approve(swapRouter, amountIn);
// execute swap
(bool success,) = swapRouter.call(swapData);
if (!success) {
revert ("swap failed!");
}
// reset approval
tokenIn.approve(swapRouter, 0);
uint256 balanceInAfter = tokenIn.balanceOf(address(this));
uint256 balanceOutAfter = tokenOut.balanceOf(address(this));
amountInDelta = balanceInBefore - balanceInAfter;
amountOutDelta = balanceOutAfter - balanceOutBefore;
// amountMin slippage checkif (amountOutDelta < amountOutMin) {
revert SlippageError();
}
// event for any swap with exact swapped valueemit Swap(address(tokenIn), address(tokenOut), amountInDelta, amountOutDelta);
}
}
// decreases liquidity from uniswap v3 positionfunction_decreaseLiquidity(INonfungiblePositionManager nfpm, uint256 tokenId, uint128 liquidity, uint256 deadline, uint256 token0Min, uint256 token1Min) internalreturns (uint256 amount0, uint256 amount1) {
if (liquidity !=0) {
(amount0, amount1) = nfpm.decreaseLiquidity(
univ3.INonfungiblePositionManager.DecreaseLiquidityParams(
tokenId,
liquidity,
token0Min,
token1Min,
deadline
)
);
}
}
// collects specified amount of fees from uniswap v3 positionfunction_collectFees(INonfungiblePositionManager nfpm, uint256 tokenId, IERC20 token0, IERC20 token1, uint128 collectAmount0, uint128 collectAmount1) internalreturns (uint256 amount0, uint256 amount1) {
uint256 balanceBefore0 = token0.balanceOf(address(this));
uint256 balanceBefore1 = token1.balanceOf(address(this));
(amount0, amount1) = nfpm.collect(
univ3.INonfungiblePositionManager.CollectParams(tokenId, address(this), collectAmount0, collectAmount1)
);
uint256 balanceAfter0 = token0.balanceOf(address(this));
uint256 balanceAfter1 = token1.balanceOf(address(this));
// reverts for fee-on-transfer tokensif (balanceAfter0 - balanceBefore0 != amount0) {
revert CollectError();
}
if (balanceAfter1 - balanceBefore1 != amount1) {
revert CollectError();
}
}
function_decreaseLiquidityAndCollectFees(DecreaseAndCollectFeesParams memory params) internalreturns (uint256 amount0, uint256 amount1) {
(uint256 positionAmount0, uint256 positionAmount1) = _decreaseLiquidity(params.nfpm, params.tokenId, params.liquidity, params.deadline, params.token0Min, params.token1Min);
(amount0, amount1) = params.nfpm.collect(
univ3.INonfungiblePositionManager.CollectParams(
params.tokenId,
address(this),
type(uint128).max,
type(uint128).max
)
);
if (!params.compoundFees) {
{
uint256 fees0Return = amount0 - positionAmount0;
uint256 fees1Return = amount1 - positionAmount1;
// return feesTokenif (fees0Return >0) {
params.token0.transfer(params.userAddress, fees0Return);
}
if (fees1Return >0) {
params.token1.transfer(params.userAddress, fees1Return);
}
}
amount0 = positionAmount0;
amount1 = positionAmount1;
}
}
function_getWeth9(INonfungiblePositionManager nfpm, Protocol protocol) viewinternalreturns (IWETH9 weth) {
if (protocol == Protocol.UNI_V3) {
weth = IWETH9(nfpm.WETH9());
} elseif (protocol == Protocol.ALGEBRA_V1) {
weth = IWETH9(nfpm.WNativeToken());
} else {
revert NotSupportedProtocol();
}
}
function_getPosition(INonfungiblePositionManager nfpm, Protocol protocol, uint256 tokenId) internalreturns (address token0, address token1, uint128 liquidity, int24 tickLower, int24 tickUpper, uint24 fee) {
(bool success, bytesmemory data) =address(nfpm).call(abi.encodeWithSignature("positions(uint256)", tokenId));
if (!success) {
revert GetPositionFailed();
}
if (protocol == Protocol.UNI_V3) {
(,, token0, token1, fee,tickLower, tickUpper, liquidity,,,,) =abi.decode(data, (uint96,address,address,address,uint24,int24,int24,uint128,uint256,uint256,uint128,uint128));
} elseif (protocol == Protocol.ALGEBRA_V1) {
(,, token0, token1, tickLower, tickUpper, liquidity,,,,) =abi.decode(data, (uint96,address,address,address,int24,int24,uint128,uint256,uint256,uint128,uint128));
}
}
/**
* @notice calculate fee
* @param emitEvent: whether to emit event or not. Since swap and mint have not had token id yet.
* we need to emit event latter
*/function_deductFees(DeductFeesParams memory params, bool emitEvent) internalreturns(uint256 amount0Left, uint256 amount1Left, uint256 amount2Left, uint256 feeAmount0, uint256 feeAmount1, uint256 feeAmount2) {
if (params.feeX64 > _maxFeeX64[params.feeType]) {
revert TooMuchFee();
}
// to save gas, we always need to check if fee exists before deductFeesif (params.feeX64 ==0) {
revert NoFees();
}
if (params.amount0 >0) {
feeAmount0 = FullMath.mulDiv(params.amount0, params.feeX64, Q64);
amount0Left = params.amount0 - feeAmount0;
}
if (params.amount1 >0) {
feeAmount1 = FullMath.mulDiv(params.amount1, params.feeX64, Q64);
amount1Left = params.amount1 - feeAmount1;
}
if (params.amount2 >0) {
feeAmount2 = FullMath.mulDiv(params.amount2, params.feeX64, Q64);
amount2Left = params.amount2 - feeAmount2;
}
if (emitEvent) {
emit DeductFees(address(params.nfpm), params.tokenId, params.userAddress, DeductFeesEventData(
params.token0, params.token1, params.token2,
params.amount0, params.amount1, params.amount2,
feeAmount0, feeAmount1, feeAmount2,
params.feeX64,
params.feeType
));
}
}
functionpause() publiconlyRole(ADMIN_ROLE) {
_pause();
}
functionunpause() publiconlyRole(ADMIN_ROLE) {
_unpause();
}
functionsetMaxFeeX64(FeeType feeType, uint64 feex64) externalonlyRole(ADMIN_ROLE) {
_maxFeeX64[feeType] = feex64;
}
functiongetMaxFeeX64(FeeType feeType) publicviewreturns (uint64) {
return _maxFeeX64[feeType];
}
}
Contract Source Code
File 4 of 24: Context.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)pragmasolidity ^0.8.20;/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
function_contextSuffixLength() internalviewvirtualreturns (uint256) {
return0;
}
}
Contract Source Code
File 5 of 24: ERC165.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/ERC165.sol)pragmasolidity ^0.8.20;import {IERC165} from"./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*/abstractcontractERC165isIERC165{
/**
* @dev See {IERC165-supportsInterface}.
*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualreturns (bool) {
return interfaceId ==type(IERC165).interfaceId;
}
}
Contract Source Code
File 6 of 24: FullMath.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;/// @title Contains 512-bit math functions/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bitslibraryFullMath{
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0/// @param a The multiplicand/// @param b The multiplier/// @param denominator The divisor/// @return result The 256-bit result/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldivfunctionmulDiv(uint256 a,
uint256 b,
uint256 denominator
) internalpurereturns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b// Compute the product mod 2**256 and mod 2**256 - 1// then use the Chinese Remainder Theorem to reconstruct// the 512 bit result. The result is stored in two 256// variables such that product = prod1 * 2**256 + prod0uint256 prod0; // Least significant 256 bits of the productuint256 prod1; // Most significant 256 bits of the productassembly {
let mm :=mulmod(a, b, not(0))
prod0 :=mul(a, b)
prod1 :=sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 divisionif (prod1 ==0) {
require(denominator >0);
assembly {
result :=div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.// Also prevents denominator == 0require(denominator > prod1);
///////////////////////////////////////////////// 512 by 256 division.///////////////////////////////////////////////// Make division exact by subtracting the remainder from [prod1 prod0]// Compute remainder using mulmoduint256 remainder;
assembly {
remainder :=mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit numberassembly {
prod1 :=sub(prod1, gt(remainder, prod0))
prod0 :=sub(prod0, remainder)
}
// Factor powers of two out of denominator// Compute largest power of two divisor of denominator.// Always >= 1.uint256 twos = (0- denominator) & denominator;
// Divide denominator by power of twoassembly {
denominator :=div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of twoassembly {
prod0 :=div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need// to flip `twos` such that it is 2**256 / twos.// If twos is zero, then it becomes oneassembly {
twos :=add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256// Now that denominator is an odd number, it has an inverse// modulo 2**256 such that denominator * inv = 1 mod 2**256.// Compute the inverse by starting with a seed that is correct// correct for four bits. That is, denominator * inv = 1 mod 2**4uint256 inv = (3* denominator) ^2;
// Now use Newton-Raphson iteration to improve the precision.// Thanks to Hensel's lifting lemma, this also works in modular// arithmetic, doubling the correct bits in each step.
inv *=2- denominator * inv; // inverse mod 2**8
inv *=2- denominator * inv; // inverse mod 2**16
inv *=2- denominator * inv; // inverse mod 2**32
inv *=2- denominator * inv; // inverse mod 2**64
inv *=2- denominator * inv; // inverse mod 2**128
inv *=2- denominator * inv; // inverse mod 2**256// Because the division is now exact we can divide by multiplying// with the modular inverse of denominator. This will give us the// correct result modulo 2**256. Since the precoditions guarantee// that the outcome is less than 2**256, this is the final result.// We don't need to compute the high bits of the result and prod1// is no longer required.
result = prod0 * inv;
return result;
}
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0/// @param a The multiplicand/// @param b The multiplier/// @param denominator The divisor/// @return result The 256-bit resultfunctionmulDivRoundingUp(uint256 a,
uint256 b,
uint256 denominator
) internalpurereturns (uint256 result) {
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) >0) {
require(result <type(uint256).max);
result++;
}
}
}
}
Contract Source Code
File 7 of 24: IAccessControl.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (access/IAccessControl.sol)pragmasolidity ^0.8.20;/**
* @dev External interface of AccessControl declared to support ERC165 detection.
*/interfaceIAccessControl{
/**
* @dev The `account` is missing a role.
*/errorAccessControlUnauthorizedAccount(address account, bytes32 neededRole);
/**
* @dev The caller of a function is not the expected one.
*
* NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
*/errorAccessControlBadConfirmation();
/**
* @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
*
* `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
* {RoleAdminChanged} not being emitted signaling this.
*/eventRoleAdminChanged(bytes32indexed role, bytes32indexed previousAdminRole, bytes32indexed newAdminRole);
/**
* @dev Emitted when `account` is granted `role`.
*
* `sender` is the account that originated the contract call, an admin role
* bearer except when using {AccessControl-_setupRole}.
*/eventRoleGranted(bytes32indexed role, addressindexed account, addressindexed sender);
/**
* @dev Emitted when `account` is revoked `role`.
*
* `sender` is the account that originated the contract call:
* - if using `revokeRole`, it is the admin role bearer
* - if using `renounceRole`, it is the role bearer (i.e. `account`)
*/eventRoleRevoked(bytes32indexed role, addressindexed account, addressindexed sender);
/**
* @dev Returns `true` if `account` has been granted `role`.
*/functionhasRole(bytes32 role, address account) externalviewreturns (bool);
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {AccessControl-_setRoleAdmin}.
*/functiongetRoleAdmin(bytes32 role) externalviewreturns (bytes32);
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/functiongrantRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*/functionrevokeRole(bytes32 role, address account) external;
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been granted `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*/functionrenounceRole(bytes32 role, address callerConfirmation) external;
}
Contract Source Code
File 8 of 24: IERC165.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)pragmasolidity ^0.8.20;/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/interfaceIERC165{
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/functionsupportsInterface(bytes4 interfaceId) externalviewreturns (bool);
}
Contract Source Code
File 9 of 24: IERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)pragmasolidity ^0.8.20;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransfer(address to, uint256 value) externalreturns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/functionallowance(address owner, address spender) externalviewreturns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/functionapprove(address spender, uint256 value) externalreturns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransferFrom(addressfrom, address to, uint256 value) externalreturns (bool);
}
Contract Source Code
File 10 of 24: IERC20Permit.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)pragmasolidity ^0.8.20;/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/interfaceIERC20Permit{
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/functionpermit(address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/functionnonces(address owner) externalviewreturns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/// solhint-disable-next-line func-name-mixedcasefunctionDOMAIN_SEPARATOR() externalviewreturns (bytes32);
}
Contract Source Code
File 11 of 24: IERC721.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721.sol)pragmasolidity ^0.8.20;import {IERC165} from"../../utils/introspection/IERC165.sol";
/**
* @dev Required interface of an ERC721 compliant contract.
*/interfaceIERC721isIERC165{
/**
* @dev Emitted when `tokenId` token is transferred from `from` to `to`.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256indexed tokenId);
/**
* @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
*/eventApproval(addressindexed owner, addressindexed approved, uint256indexed tokenId);
/**
* @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
*/eventApprovalForAll(addressindexed owner, addressindexed operator, bool approved);
/**
* @dev Returns the number of tokens in ``owner``'s account.
*/functionbalanceOf(address owner) externalviewreturns (uint256 balance);
/**
* @dev Returns the owner of the `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/functionownerOf(uint256 tokenId) externalviewreturns (address owner);
/**
* @dev Safely transfers `tokenId` token from `from` to `to`.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/functionsafeTransferFrom(addressfrom, address to, uint256 tokenId, bytescalldata data) external;
/**
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
* are aware of the ERC721 protocol to prevent tokens from being forever locked.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must exist and be owned by `from`.
* - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
* {setApprovalForAll}.
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
* a safe transfer.
*
* Emits a {Transfer} event.
*/functionsafeTransferFrom(addressfrom, address to, uint256 tokenId) external;
/**
* @dev Transfers `tokenId` token from `from` to `to`.
*
* WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
* or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
* understand this adds an external call which potentially creates a reentrancy vulnerability.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
* - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
*
* Emits a {Transfer} event.
*/functiontransferFrom(addressfrom, address to, uint256 tokenId) external;
/**
* @dev Gives permission to `to` to transfer `tokenId` token to another account.
* The approval is cleared when the token is transferred.
*
* Only a single account can be approved at a time, so approving the zero address clears previous approvals.
*
* Requirements:
*
* - The caller must own the token or be an approved operator.
* - `tokenId` must exist.
*
* Emits an {Approval} event.
*/functionapprove(address to, uint256 tokenId) external;
/**
* @dev Approve or remove `operator` as an operator for the caller.
* Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
*
* Requirements:
*
* - The `operator` cannot be the address zero.
*
* Emits an {ApprovalForAll} event.
*/functionsetApprovalForAll(address operator, bool approved) external;
/**
* @dev Returns the account approved for `tokenId` token.
*
* Requirements:
*
* - `tokenId` must exist.
*/functiongetApproved(uint256 tokenId) externalviewreturns (address operator);
/**
* @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
*
* See {setApprovalForAll}
*/functionisApprovedForAll(address owner, address operator) externalviewreturns (bool);
}
Contract Source Code
File 12 of 24: IERC721Enumerable.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Enumerable.sol)pragmasolidity ^0.8.20;import {IERC721} from"../IERC721.sol";
/**
* @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
* @dev See https://eips.ethereum.org/EIPS/eip-721
*/interfaceIERC721EnumerableisIERC721{
/**
* @dev Returns the total amount of tokens stored by the contract.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns a token ID owned by `owner` at a given `index` of its token list.
* Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
*/functiontokenOfOwnerByIndex(address owner, uint256 index) externalviewreturns (uint256);
/**
* @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
* Use along with {totalSupply} to enumerate all tokens.
*/functiontokenByIndex(uint256 index) externalviewreturns (uint256);
}
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.7.5;import'@openzeppelin/contracts/token/ERC721/IERC721.sol';
/// @title ERC721 with permit/// @notice Extension to ERC721 that includes a permit function for signature based approvalsinterfaceIERC721PermitisIERC721{
/// @notice The permit typehash used in the permit signature/// @return The typehash for the permitfunctionPERMIT_TYPEHASH() externalpurereturns (bytes32);
/// @notice The domain separator used in the permit signature/// @return The domain seperator used in encoding of permit signaturefunctionDOMAIN_SEPARATOR() externalviewreturns (bytes32);
/// @notice Approve of a specific token ID for spending by spender via signature/// @param spender The account that is being approved/// @param tokenId The ID of the token that is being approved for spending/// @param deadline The deadline timestamp by which the call must be mined for the approve to work/// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s`/// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s`/// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v`functionpermit(address spender,
uint256 tokenId,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) externalpayable;
}
Contract Source Code
File 15 of 24: IERC721Receiver.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)pragmasolidity ^0.8.20;/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/interfaceIERC721Receiver{
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/functiononERC721Received(address operator,
addressfrom,
uint256 tokenId,
bytescalldata data
) externalreturns (bytes4);
}
Contract Source Code
File 16 of 24: INonfungiblePositionManager.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.7.5;pragmaabicoderv2;import'@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol';
import'@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol';
import'./IPoolInitializer.sol';
import'./IERC721Permit.sol';
import'./IPeripheryPayments.sol';
import'./IPeripheryImmutableState.sol';
import'../libraries/PoolAddress.sol';
/// @title Non-fungible token for positions/// @notice Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred/// and authorized.interfaceINonfungiblePositionManagerisIPoolInitializer,
IPeripheryPayments,
IPeripheryImmutableState,
IERC721Metadata,
IERC721Enumerable,
IERC721Permit{
/// @notice Emitted when liquidity is increased for a position NFT/// @dev Also emitted when a token is minted/// @param tokenId The ID of the token for which liquidity was increased/// @param liquidity The amount by which liquidity for the NFT position was increased/// @param amount0 The amount of token0 that was paid for the increase in liquidity/// @param amount1 The amount of token1 that was paid for the increase in liquidityeventIncreaseLiquidity(uint256indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Emitted when liquidity is decreased for a position NFT/// @param tokenId The ID of the token for which liquidity was decreased/// @param liquidity The amount by which liquidity for the NFT position was decreased/// @param amount0 The amount of token0 that was accounted for the decrease in liquidity/// @param amount1 The amount of token1 that was accounted for the decrease in liquidityeventDecreaseLiquidity(uint256indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
/// @notice Emitted when tokens are collected for a position NFT/// @dev The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior/// @param tokenId The ID of the token for which underlying tokens were collected/// @param recipient The address of the account that received the collected tokens/// @param amount0 The amount of token0 owed to the position that was collected/// @param amount1 The amount of token1 owed to the position that was collectedeventCollect(uint256indexed tokenId, address recipient, uint256 amount0, uint256 amount1);
/// @notice Returns the position information associated with a given token ID./// @dev Throws if the token ID is not valid./// @param tokenId The ID of the token that represents the position/// @return nonce The nonce for permits/// @return operator The address that is approved for spending/// @return token0 The address of the token0 for a specific pool/// @return token1 The address of the token1 for a specific pool/// @return fee The fee associated with the pool/// @return tickLower The lower end of the tick range for the position/// @return tickUpper The higher end of the tick range for the position/// @return liquidity The liquidity of the position/// @return feeGrowthInside0LastX128 The fee growth of token0 as of the last action on the individual position/// @return feeGrowthInside1LastX128 The fee growth of token1 as of the last action on the individual position/// @return tokensOwed0 The uncollected amount of token0 owed to the position as of the last computation/// @return tokensOwed1 The uncollected amount of token1 owed to the position as of the last computationfunctionpositions(uint256 tokenId)
externalviewreturns (uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
structMintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
/// @notice Creates a new position wrapped in a NFT/// @dev Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized/// a method does not exist, i.e. the pool is assumed to be initialized./// @param params The params necessary to mint a position, encoded as `MintParams` in calldata/// @return tokenId The ID of the token that represents the minted position/// @return liquidity The amount of liquidity for this position/// @return amount0 The amount of token0/// @return amount1 The amount of token1functionmint(MintParams calldata params)
externalpayablereturns (uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
structIncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Increases the amount of liquidity in a position, with tokens paid by the `msg.sender`/// @param params tokenId The ID of the token for which liquidity is being increased,/// amount0Desired The desired amount of token0 to be spent,/// amount1Desired The desired amount of token1 to be spent,/// amount0Min The minimum amount of token0 to spend, which serves as a slippage check,/// amount1Min The minimum amount of token1 to spend, which serves as a slippage check,/// deadline The time by which the transaction must be included to effect the change/// @return liquidity The new liquidity amount as a result of the increase/// @return amount0 The amount of token0 to acheive resulting liquidity/// @return amount1 The amount of token1 to acheive resulting liquidityfunctionincreaseLiquidity(IncreaseLiquidityParams calldata params)
externalpayablereturns (uint128 liquidity,
uint256 amount0,
uint256 amount1
);
structDecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
/// @notice Decreases the amount of liquidity in a position and accounts it to the position/// @param params tokenId The ID of the token for which liquidity is being decreased,/// amount The amount by which liquidity will be decreased,/// amount0Min The minimum amount of token0 that should be accounted for the burned liquidity,/// amount1Min The minimum amount of token1 that should be accounted for the burned liquidity,/// deadline The time by which the transaction must be included to effect the change/// @return amount0 The amount of token0 accounted to the position's tokens owed/// @return amount1 The amount of token1 accounted to the position's tokens owedfunctiondecreaseLiquidity(DecreaseLiquidityParams calldata params)
externalpayablereturns (uint256 amount0, uint256 amount1);
structCollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
/// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient/// @param params tokenId The ID of the NFT for which tokens are being collected,/// recipient The account that should receive the tokens,/// amount0Max The maximum amount of token0 to collect,/// amount1Max The maximum amount of token1 to collect/// @return amount0 The amount of fees collected in token0/// @return amount1 The amount of fees collected in token1functioncollect(CollectParams calldata params) externalpayablereturns (uint256 amount0, uint256 amount1);
/// @notice Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens/// must be collected first./// @param tokenId The ID of the token that is being burnedfunctionburn(uint256 tokenId) externalpayable;
}
Contract Source Code
File 17 of 24: IPeripheryImmutableState.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.5.0;/// @title Immutable state/// @notice Functions that return immutable state of the routerinterfaceIPeripheryImmutableState{
/// @return Returns the address of the Uniswap V3 factoryfunctionfactory() externalviewreturns (address);
/// @return Returns the address of WETH9functionWETH9() externalviewreturns (address);
}
Contract Source Code
File 18 of 24: IPeripheryPayments.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.7.5;/// @title Periphery Payments/// @notice Functions to ease deposits and withdrawals of ETHinterfaceIPeripheryPayments{
/// @notice Unwraps the contract's WETH9 balance and sends it to recipient as ETH./// @dev The amountMinimum parameter prevents malicious contracts from stealing WETH9 from users./// @param amountMinimum The minimum amount of WETH9 to unwrap/// @param recipient The address receiving ETHfunctionunwrapWETH9(uint256 amountMinimum, address recipient) externalpayable;
/// @notice Refunds any ETH balance held by this contract to the `msg.sender`/// @dev Useful for bundling with mint or increase liquidity that uses ether, or exact output swaps/// that use ether for the input amountfunctionrefundETH() externalpayable;
/// @notice Transfers the full amount of a token held by this contract to recipient/// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users/// @param token The contract address of the token which will be transferred to `recipient`/// @param amountMinimum The minimum amount of token required for a transfer/// @param recipient The destination address of the tokenfunctionsweepToken(address token,
uint256 amountMinimum,
address recipient
) externalpayable;
}
Contract Source Code
File 19 of 24: IPoolInitializer.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.7.5;pragmaabicoderv2;/// @title Creates and initializes V3 Pools/// @notice Provides a method for creating and initializing a pool, if necessary, for bundling with other methods that/// require the pool to exist.interfaceIPoolInitializer{
/// @notice Creates a new pool if it does not exist, then initializes if not initialized/// @dev This method can be bundled with others via IMulticall for the first action (e.g. mint) performed against a pool/// @param token0 The contract address of token0 of the pool/// @param token1 The contract address of token1 of the pool/// @param fee The fee amount of the v3 pool for the specified token pair/// @param sqrtPriceX96 The initial square root price of the pool as a Q64.96 value/// @return pool Returns the pool address based on the pair of tokens and fee, will return the newly created pool address if necessaryfunctioncreateAndInitializePoolIfNecessary(address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) externalpayablereturns (address pool);
}
Contract Source Code
File 20 of 24: IWETH9.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.8.15;import'@openzeppelin/contracts/token/ERC20/IERC20.sol';
/// @title Interface for WETH9interfaceIWETH9isIERC20{
/// @notice Deposit ether to get wrapped etherfunctiondeposit() externalpayable;
/// @notice Withdraw wrapped ether to get etherfunctionwithdraw(uint256) external;
}
Contract Source Code
File 21 of 24: Pausable.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)pragmasolidity ^0.8.20;import {Context} from"../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/abstractcontractPausableisContext{
boolprivate _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/eventPaused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/eventUnpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/errorEnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/errorExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/constructor() {
_paused =false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/modifierwhenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/modifierwhenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/functionpaused() publicviewvirtualreturns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/function_requireNotPaused() internalviewvirtual{
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/function_requirePaused() internalviewvirtual{
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/function_pause() internalvirtualwhenNotPaused{
_paused =true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/function_unpause() internalvirtualwhenPaused{
_paused =false;
emit Unpaused(_msgSender());
}
}
Contract Source Code
File 22 of 24: PoolAddress.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.5.0;/// @title Provides functions for deriving a pool address from the factory, tokens, and the feelibraryPoolAddress{
bytes32internalconstant POOL_INIT_CODE_HASH =0xa598dd2fba360510c5a8f02f44423a4468e902df5857dbce3ca162a43a3a31ff;
/// @notice The identifying key of the poolstructPoolKey {
address token0;
address token1;
uint24 fee;
}
/// @notice Returns PoolKey: the ordered tokens with the matched fee levels/// @param tokenA The first token of a pool, unsorted/// @param tokenB The second token of a pool, unsorted/// @param fee The fee level of the pool/// @return Poolkey The pool details with ordered token0 and token1 assignmentsfunctiongetPoolKey(address tokenA,
address tokenB,
uint24 fee
) internalpurereturns (PoolKey memory) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
return PoolKey({token0: tokenA, token1: tokenB, fee: fee});
}
/// @notice Deterministically computes the pool address given the factory and PoolKey/// @param factory The Uniswap V3 factory contract address/// @param key The PoolKey/// @return pool The contract address of the V3 poolfunctioncomputeAddress(address factory, PoolKey memory key) internalpurereturns (address pool) {
require(key.token0 < key.token1);
pool =address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encode(key.token0, key.token1, key.fee)),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
}
Contract Source Code
File 23 of 24: SafeERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)pragmasolidity ^0.8.20;import {IERC20} from"../IERC20.sol";
import {IERC20Permit} from"../extensions/IERC20Permit.sol";
import {Address} from"../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/librarySafeERC20{
usingAddressforaddress;
/**
* @dev An operation with an ERC20 token failed.
*/errorSafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/errorSafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeTransfer(IERC20 token, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/functionsafeTransferFrom(IERC20 token, addressfrom, address to, uint256 value) internal{
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/functionsafeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal{
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/functionsafeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal{
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/functionforceApprove(IERC20 token, address spender, uint256 value) internal{
bytesmemory approvalCall =abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/function_callOptionalReturn(IERC20 token, bytesmemory data) private{
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that// the target address contains contract code and also asserts for success in the low-level call.bytesmemory returndata =address(token).functionCall(data);
if (returndata.length!=0&&!abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/function_callOptionalReturnBool(IERC20 token, bytesmemory data) privatereturns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false// and not revert is the subcall reverts.
(bool success, bytesmemory returndata) =address(token).call(data);
return success && (returndata.length==0||abi.decode(returndata, (bool))) &&address(token).code.length>0;
}
}
Contract Source Code
File 24 of 24: V3Utils.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import"./Common.sol";
/// @title v3Utils v1.0/// @notice Utility functions for Uniswap V3 positions/// This is a completely ownerless/stateless contract - does not hold any ERC20 or NFTs./// It can be simply redeployed when new / better functionality is implementedcontractV3UtilsisIERC721Receiver, Common{
/// @notice Action which should be executed on provided NFTenumWhatToDo {
CHANGE_RANGE,
WITHDRAW_AND_COLLECT_AND_SWAP,
COMPOUND_FEES
}
/// @notice Complete description of what should be executed on provided NFT - different fields are used depending on specified WhatToDo structInstructions {
// what action to perform on provided Uniswap v3 position
WhatToDo whatToDo;
// protocol to provide lp
Protocol protocol;
// target token for swaps (if this is address(0) no swaps are executed)address targetToken;
// for removing liquidity slippageuint256 amountRemoveMin0;
uint256 amountRemoveMin1;
// amountIn0 is used for swap and also as minAmount0 for decreased liquidity + collected feesuint256 amountIn0;
// if token0 needs to be swapped to targetToken - set valuesuint256 amountOut0Min;
bytes swapData0; // encoded data from 0x api call (address,bytes) - allowanceTarget,data// amountIn1 is used for swap and also as minAmount1 for decreased liquidity + collected feesuint256 amountIn1;
// if token1 needs to be swapped to targetToken - set valuesuint256 amountOut1Min;
bytes swapData1; // encoded data from 0x api call (address,bytes) - allowanceTarget,data// for creating new positions with CHANGE_RANGEint24 tickLower;
int24 tickUpper;
bool compoundFees;
// remove liquidity amount for COMPOUND_FEES (in this case should be probably 0) / CHANGE_RANGE / WITHDRAW_AND_COLLECT_AND_SWAPuint128 liquidity;
// for adding liquidity slippageuint256 amountAddMin0;
uint256 amountAddMin1;
// for all uniswap deadlineable functionsuint256 deadline;
// left over tokens will be sent to this addressaddress recipient;
// if tokenIn or tokenOut is WETH - unwrapbool unwrap;
// protocol feesuint64 protocolFeeX64;
}
/// @notice Execute instruction by pulling approved NFT instead of direct safeTransferFrom call from owner/// @param tokenId Token to process/// @param instructions Instructions to executefunctionexecute(INonfungiblePositionManager _nfpm, uint256 tokenId, Instructions calldata instructions) whenNotPaused() external{
// must be approved beforehand
_nfpm.safeTransferFrom(
msg.sender,
address(this),
tokenId,
abi.encode(instructions)
);
}
/// @notice ERC721 callback function. Called on safeTransferFrom and does manipulation as configured in encoded Instructions parameter. /// At the end the NFT (and any newly minted NFT) is returned to sender. The leftover tokens are sent to instructions.recipient.functiononERC721Received(address, addressfrom, uint256 tokenId, bytescalldata data) whenNotPaused() externaloverridereturns (bytes4) {
INonfungiblePositionManager nfpm = INonfungiblePositionManager(msg.sender);
// not allowed to send to itselfif (from==address(this)) {
revert SelfSend();
}
Instructions memory instructions =abi.decode(data, (Instructions));
(address token0,address token1,,,,uint24 fee) = _getPosition(nfpm, instructions.protocol, tokenId);
(uint256 amount0, uint256 amount1) = _decreaseLiquidityAndCollectFees(DecreaseAndCollectFeesParams(nfpm, instructions.recipient, IERC20(token0), IERC20(token1), tokenId, instructions.liquidity, instructions.deadline, instructions.amountRemoveMin0, instructions.amountRemoveMin1, instructions.compoundFees));
// take protocol feesif (instructions.protocolFeeX64 >0) {
(amount0, amount1,,,,) = _deductFees(DeductFeesParams(amount0, amount1, 0, instructions.protocolFeeX64, FeeType.PROTOCOL_FEE, address(nfpm), tokenId, instructions.recipient, token0, token1, address(0)), true);
}
// check if enough tokens are available for swapsif (amount0 < instructions.amountIn0 || amount1 < instructions.amountIn1) {
revert AmountError();
}
if (instructions.whatToDo == WhatToDo.COMPOUND_FEES) {
SwapAndIncreaseLiquidityResult memory result;
if (instructions.targetToken == token0) {
result = _swapAndIncrease(SwapAndIncreaseLiquidityParams(instructions.protocol, nfpm, tokenId, amount0, amount1, 0, instructions.recipient, instructions.deadline, IERC20(token1), instructions.amountIn1, instructions.amountOut1Min, instructions.swapData1, 0, 0, "", instructions.amountAddMin0, instructions.amountAddMin1, 0), IERC20(token0), IERC20(token1), instructions.unwrap);
} elseif (instructions.targetToken == token1) {
result = _swapAndIncrease(SwapAndIncreaseLiquidityParams(instructions.protocol, nfpm, tokenId, amount0, amount1, 0, instructions.recipient, instructions.deadline, IERC20(token0), 0, 0, "", instructions.amountIn0, instructions.amountOut0Min, instructions.swapData0, instructions.amountAddMin0, instructions.amountAddMin1, 0), IERC20(token0), IERC20(token1), instructions.unwrap);
} else {
// no swap is done here
result = _swapAndIncrease(SwapAndIncreaseLiquidityParams(instructions.protocol, nfpm, tokenId, amount0, amount1, 0, instructions.recipient, instructions.deadline, IERC20(address(0)), 0, 0, "", 0, 0, "", instructions.amountAddMin0, instructions.amountAddMin1, 0), IERC20(token0), IERC20(token1), instructions.unwrap);
}
emit CompoundFees(address(nfpm), tokenId, result.liquidity, result.added0, result.added1);
} elseif (instructions.whatToDo == WhatToDo.CHANGE_RANGE) {
SwapAndMintResult memory result;
if (instructions.targetToken == token0) {
result = _swapAndMint(SwapAndMintParams(instructions.protocol, nfpm, IERC20(token0), IERC20(token1), fee, instructions.tickLower, instructions.tickUpper, 0, amount0, amount1, 0, instructions.recipient, instructions.deadline, IERC20(token1), instructions.amountIn1, instructions.amountOut1Min, instructions.swapData1, 0, 0, "", instructions.amountAddMin0, instructions.amountAddMin1), instructions.unwrap);
} elseif (instructions.targetToken == token1) {
result = _swapAndMint(SwapAndMintParams(instructions.protocol, nfpm, IERC20(token0), IERC20(token1), fee, instructions.tickLower, instructions.tickUpper, 0, amount0, amount1, 0, instructions.recipient, instructions.deadline, IERC20(token0), 0, 0, "", instructions.amountIn0, instructions.amountOut0Min, instructions.swapData0, instructions.amountAddMin0, instructions.amountAddMin1), instructions.unwrap);
} else {
// no swap is done here
result = _swapAndMint(SwapAndMintParams(instructions.protocol, nfpm, IERC20(token0), IERC20(token1), fee, instructions.tickLower, instructions.tickUpper, 0, amount0, amount1, 0, instructions.recipient, instructions.deadline, IERC20(address(0)), 0, 0, "", 0, 0, "", instructions.amountAddMin0, instructions.amountAddMin1), instructions.unwrap);
}
emit ChangeRange(msg.sender, tokenId, result.tokenId, result.liquidity, result.added0, result.added1);
} elseif (instructions.whatToDo == WhatToDo.WITHDRAW_AND_COLLECT_AND_SWAP) {
IWETH9 weth = _getWeth9(nfpm, instructions.protocol);
uint256 targetAmount;
if (token0 != instructions.targetToken) {
(uint256 amountInDelta, uint256 amountOutDelta) = _swap(IERC20(token0), IERC20(instructions.targetToken), amount0, instructions.amountOut0Min, instructions.swapData0);
if (amountInDelta < amount0) {
_transferToken(weth, instructions.recipient, IERC20(token0), amount0 - amountInDelta, instructions.unwrap);
}
targetAmount += amountOutDelta;
} else {
targetAmount += amount0;
}
if (token1 != instructions.targetToken) {
(uint256 amountInDelta, uint256 amountOutDelta) = _swap(IERC20(token1), IERC20(instructions.targetToken), amount1, instructions.amountOut1Min, instructions.swapData1);
if (amountInDelta < amount1) {
_transferToken(weth, instructions.recipient, IERC20(token1), amount1 - amountInDelta, instructions.unwrap);
}
targetAmount += amountOutDelta;
} else {
targetAmount += amount1;
}
// send complete target amountif (targetAmount !=0&& instructions.targetToken !=address(0)) {
_transferToken(weth, instructions.recipient, IERC20(instructions.targetToken), targetAmount, instructions.unwrap);
}
emit WithdrawAndCollectAndSwap(address(nfpm), tokenId, instructions.targetToken, targetAmount);
} else {
revert NotSupportedAction();
}
// return token to owner (this line guarantees that token is returned to originating owner)
nfpm.transferFrom(address(this), from, tokenId);
return IERC721Receiver.onERC721Received.selector;
}
/// @notice Params for swap() functionstructSwapParams {
IWETH9 weth;
IERC20 tokenIn;
IERC20 tokenOut;
uint256 amountIn;
uint256 minAmountOut;
address recipient; // recipient of tokenOut and leftover tokenIn (if any leftover)bytes swapData;
bool unwrap; // if tokenIn or tokenOut is WETH - unwrap
}
/// @notice Swaps amountIn of tokenIn for tokenOut - returning at least minAmountOut/// @param params Swap configuration/// If tokenIn is wrapped native token - both the token or the wrapped token can be sent (the sum of both must be equal to amountIn)/// Optionally unwraps any wrapped native token and returns native token insteadfunctionswap(SwapParams calldata params) whenNotPaused() externalpayablereturns (uint256 amountOut) {
if (params.tokenIn == params.tokenOut) {
revert SameToken();
}
_prepareSwap(params.weth, params.tokenIn, IERC20(address(0)), IERC20(address(0)), params.amountIn, 0, 0);
uint256 amountInDelta;
(amountInDelta, amountOut) = _swap(params.tokenIn, params.tokenOut, params.amountIn, params.minAmountOut, params.swapData);
// send swapped amount of tokenOutif (amountOut !=0) {
_transferToken(params.weth, params.recipient, params.tokenOut, amountOut, params.unwrap);
}
// if not all was swapped - return leftovers of tokenInuint256 leftOver = params.amountIn - amountInDelta;
if (leftOver !=0) {
_transferToken(params.weth, params.recipient, params.tokenIn, leftOver, params.unwrap);
}
}
/// @notice Does 1 or 2 swaps from swapSourceToken to token0 and token1 and adds as much as possible liquidity to a newly minted position./// @param params Swap and mint configuration/// Newly minted NFT and leftover tokens are returned to recipientfunctionswapAndMint(SwapAndMintParams calldata params) whenNotPaused() externalpayablereturns (SwapAndMintResult memory result) {
if (params.token0 == params.token1) {
revert SameToken();
}
IWETH9 weth = _getWeth9(params.nfpm, params.protocol);
_prepareSwap(weth, params.token0, params.token1, params.swapSourceToken, params.amount0, params.amount1, params.amount2);
SwapAndMintParams memory _params = params;
DeductFeesEventData memory eventData;
if (params.protocolFeeX64 >0) {
uint256 feeAmount0;
uint256 feeAmount1;
uint256 feeAmount2;
// since we do not have the tokenId here, we need to emit event later
(_params.amount0, _params.amount1, _params.amount2, feeAmount0, feeAmount1, feeAmount2) = _deductFees(DeductFeesParams(params.amount0, params.amount1, params.amount2, params.protocolFeeX64, FeeType.PROTOCOL_FEE, address(params.nfpm), 0, params.recipient, address(params.token0), address(params.token1), address(params.swapSourceToken)), false);
// swap source token is not token 0 and token 1if (_params.swapSourceToken != _params.token0 && _params.swapSourceToken != _params.token1) {
if (_params.amountIn0 + _params.amountIn1 > _params.amount2) {
revert AmountError();
}
if (_params.amountIn0 + _params.amountIn1 < _params.amount2) {
uint256 leftOverAmount = _params.amount2 - (_params.amountIn0 + _params.amountIn1);
// return un-needed tokens
_transferToken(weth, msg.sender, _params.swapSourceToken, leftOverAmount, msg.value!=0);
}
}
eventData = DeductFeesEventData({
token0: address(params.token0),
token1: address(params.token1),
token2: address(params.swapSourceToken),
amount0: params.amount0,
amount1: params.amount1,
amount2: params.amount2,
feeAmount0: feeAmount0,
feeAmount1: feeAmount1,
feeAmount2: feeAmount2,
feeX64: params.protocolFeeX64,
feeType: FeeType.PROTOCOL_FEE
});
}
result = _swapAndMint(_params, msg.value!=0);
emit DeductFees(address(params.nfpm), result.tokenId, params.recipient, eventData);
}
/// @notice Does 1 or 2 swaps from swapSourceToken to token0 and token1 and adds as much as possible liquidity to any existing position (no need to be position owner)./// @param params Swap and increase liquidity configuration// Sends any leftover tokens to recipient.functionswapAndIncreaseLiquidity(SwapAndIncreaseLiquidityParams calldata params) whenNotPaused() externalpayablereturns (SwapAndIncreaseLiquidityResult memory result) {
address owner = params.nfpm.ownerOf(params.tokenId);
require(owner ==msg.sender, "sender is not owner of position");
(address token0,address token1,,,,) = _getPosition(params.nfpm, params.protocol, params.tokenId);
IWETH9 weth = _getWeth9(params.nfpm, params.protocol);
_prepareSwap(weth, IERC20(token0), IERC20(token1), params.swapSourceToken, params.amount0, params.amount1, params.amount2);
SwapAndIncreaseLiquidityParams memory _params = params;
if (params.protocolFeeX64 >0) {
(_params.amount0, _params.amount1, _params.amount2,,,) = _deductFees(DeductFeesParams(params.amount0, params.amount1, params.amount2, params.protocolFeeX64, FeeType.PROTOCOL_FEE, address(params.nfpm), params.tokenId, params.recipient, token0, token1, address(params.swapSourceToken)), true);
// swap source token is not token 0 and token 1if (address(_params.swapSourceToken) != token0 &&address(_params.swapSourceToken) != token1) {
if (_params.amountIn0 + _params.amountIn1 > _params.amount2) {
revert AmountError();
}
if (_params.amountIn0 + _params.amountIn1 < _params.amount2) {
uint256 leftOverAmount = _params.amount2 - (_params.amountIn0 + _params.amountIn1);
// return un-needed tokens
_transferToken(weth, msg.sender, _params.swapSourceToken, leftOverAmount, msg.value!=0);
}
}
}
result = _swapAndIncrease(_params, IERC20(token0), IERC20(token1), msg.value!=0);
}
// needed for WETH unwrappingreceive() externalpayable{}
}