From bb2ee8761b43ee472917af36369df056aae19265 Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 14:37:20 +0700 Subject: [PATCH 01/22] chore: forge fmt --- script/utils/Contract.sol | 4 +- src/aggregate-router/UniversalRouter.sol | 132 +-- src/aggregate-router/base/Callbacks.sol | 40 +- src/aggregate-router/base/Dispatcher.sol | 805 +++++++++--------- .../base/LockAndMsgSender.sol | 48 +- .../base/RewardsCollector.sol | 30 +- .../base/RouterImmutables.sol | 40 +- .../deploy/UnsupportedProtocol.sol | 8 +- .../interfaces/IRewardsCollector.sol | 8 +- .../interfaces/IUniversalRouter.sol | 32 +- .../external/ICryptoPunksMarket.sol | 8 +- .../interfaces/external/IWETH9.sol | 10 +- src/aggregate-router/libraries/Commands.sol | 114 +-- src/aggregate-router/libraries/Constants.sol | 46 +- .../modules/NFTImmutables.sol | 104 +-- src/aggregate-router/modules/Payments.sol | 230 ++--- .../modules/PaymentsImmutables.sol | 48 +- .../modules/Permit2Payments.sol | 66 +- .../modules/uniswap/UniswapImmutables.sol | 36 +- .../modules/uniswap/v2/UniswapV2Library.sol | 260 +++--- .../modules/uniswap/v2/V2SwapRouter.sol | 158 ++-- .../modules/uniswap/v3/BytesLib.sol | 144 ++-- .../modules/uniswap/v3/V3Path.sol | 62 +- .../modules/uniswap/v3/V3SwapRouter.sol | 321 ++++--- 24 files changed, 1369 insertions(+), 1385 deletions(-) diff --git a/script/utils/Contract.sol b/script/utils/Contract.sol index bfee534..2877156 100644 --- a/script/utils/Contract.sol +++ b/script/utils/Contract.sol @@ -3,7 +3,9 @@ pragma solidity ^0.8.23; import { LibString, TContract } from "@fdk/types/Types.sol"; -enum Contract { Counter } +enum Contract { + Counter +} using { key, name } for Contract global; diff --git a/src/aggregate-router/UniversalRouter.sol b/src/aggregate-router/UniversalRouter.sol index c54d50e..164167e 100644 --- a/src/aggregate-router/UniversalRouter.sol +++ b/src/aggregate-router/UniversalRouter.sol @@ -2,83 +2,83 @@ pragma solidity ^0.8.17; // Command implementations -import {Dispatcher} from './base/Dispatcher.sol'; -import {RewardsCollector} from './base/RewardsCollector.sol'; -import {RouterParameters} from './base/RouterImmutables.sol'; -import {PaymentsImmutables, PaymentsParameters} from './modules/PaymentsImmutables.sol'; -import {NFTImmutables, NFTParameters} from './modules/NFTImmutables.sol'; -import {UniswapImmutables, UniswapParameters} from './modules/uniswap/UniswapImmutables.sol'; -import {Commands} from './libraries/Commands.sol'; -import {IUniversalRouter} from './interfaces/IUniversalRouter.sol'; +import { Dispatcher } from "./base/Dispatcher.sol"; +import { RewardsCollector } from "./base/RewardsCollector.sol"; +import { RouterParameters } from "./base/RouterImmutables.sol"; +import { PaymentsImmutables, PaymentsParameters } from "./modules/PaymentsImmutables.sol"; +import { NFTImmutables, NFTParameters } from "./modules/NFTImmutables.sol"; +import { UniswapImmutables, UniswapParameters } from "./modules/uniswap/UniswapImmutables.sol"; +import { Commands } from "./libraries/Commands.sol"; +import { IUniversalRouter } from "./interfaces/IUniversalRouter.sol"; contract UniversalRouter is IUniversalRouter, Dispatcher, RewardsCollector { - modifier checkDeadline(uint256 deadline) { - if (block.timestamp > deadline) revert TransactionDeadlinePassed(); - _; - } + modifier checkDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert TransactionDeadlinePassed(); + _; + } - constructor(RouterParameters memory params) - UniswapImmutables( - UniswapParameters(params.v2Factory, params.v3Factory, params.pairInitCodeHash, params.poolInitCodeHash) - ) - PaymentsImmutables(PaymentsParameters(params.permit2, params.weth9, params.openseaConduit, params.sudoswap)) - NFTImmutables( - NFTParameters( - params.seaportV1_5, - params.seaportV1_4, - params.nftxZap, - params.x2y2, - params.foundation, - params.sudoswap, - params.elementMarket, - params.nft20Zap, - params.cryptopunks, - params.looksRareV2, - params.routerRewardsDistributor, - params.looksRareRewardsDistributor, - params.looksRareToken - ) - ) - {} + constructor(RouterParameters memory params) + UniswapImmutables( + UniswapParameters(params.v2Factory, params.v3Factory, params.pairInitCodeHash, params.poolInitCodeHash) + ) + PaymentsImmutables(PaymentsParameters(params.permit2, params.weth9, params.openseaConduit, params.sudoswap)) + NFTImmutables( + NFTParameters( + params.seaportV1_5, + params.seaportV1_4, + params.nftxZap, + params.x2y2, + params.foundation, + params.sudoswap, + params.elementMarket, + params.nft20Zap, + params.cryptopunks, + params.looksRareV2, + params.routerRewardsDistributor, + params.looksRareRewardsDistributor, + params.looksRareToken + ) + ) + { } - /// @inheritdoc IUniversalRouter - function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) - external - payable - checkDeadline(deadline) - { - execute(commands, inputs); - } + /// @inheritdoc IUniversalRouter + function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) + external + payable + checkDeadline(deadline) + { + execute(commands, inputs); + } - /// @inheritdoc Dispatcher - function execute(bytes calldata commands, bytes[] calldata inputs) public payable override isNotLocked { - bool success; - bytes memory output; - uint256 numCommands = commands.length; - if (inputs.length != numCommands) revert LengthMismatch(); + /// @inheritdoc Dispatcher + function execute(bytes calldata commands, bytes[] calldata inputs) public payable override isNotLocked { + bool success; + bytes memory output; + uint256 numCommands = commands.length; + if (inputs.length != numCommands) revert LengthMismatch(); - // loop through all given commands, execute them and pass along outputs as defined - for (uint256 commandIndex = 0; commandIndex < numCommands;) { - bytes1 command = commands[commandIndex]; + // loop through all given commands, execute them and pass along outputs as defined + for (uint256 commandIndex = 0; commandIndex < numCommands;) { + bytes1 command = commands[commandIndex]; - bytes calldata input = inputs[commandIndex]; + bytes calldata input = inputs[commandIndex]; - (success, output) = dispatch(command, input); + (success, output) = dispatch(command, input); - if (!success && successRequired(command)) { - revert ExecutionFailed({commandIndex: commandIndex, message: output}); - } + if (!success && successRequired(command)) { + revert ExecutionFailed({ commandIndex: commandIndex, message: output }); + } - unchecked { - commandIndex++; - } - } + unchecked { + commandIndex++; + } } + } - function successRequired(bytes1 command) internal pure returns (bool) { - return command & Commands.FLAG_ALLOW_REVERT == 0; - } + function successRequired(bytes1 command) internal pure returns (bool) { + return command & Commands.FLAG_ALLOW_REVERT == 0; + } - /// @notice To receive ETH from WETH and NFT protocols - receive() external payable {} + /// @notice To receive ETH from WETH and NFT protocols + receive() external payable { } } diff --git a/src/aggregate-router/base/Callbacks.sol b/src/aggregate-router/base/Callbacks.sol index fa097b5..a3d33c0 100644 --- a/src/aggregate-router/base/Callbacks.sol +++ b/src/aggregate-router/base/Callbacks.sol @@ -1,32 +1,32 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; -import {IERC1155Receiver} from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol'; -import {IERC165} from '@openzeppelin/contracts/utils/introspection/IERC165.sol'; +import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /// @title ERC Callback Support /// @notice Implements various functions introduced by a variety of ERCs for security reasons. /// All are called by external contracts to ensure that this contract safely supports the ERC in question. contract Callbacks is IERC721Receiver, IERC1155Receiver { - function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { - return this.onERC721Received.selector; - } + function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) { + return this.onERC721Received.selector; + } - function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { - return this.onERC1155Received.selector; - } + function onERC1155Received(address, address, uint256, uint256, bytes calldata) external pure returns (bytes4) { + return this.onERC1155Received.selector; + } - function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) - external - pure - returns (bytes4) - { - return this.onERC1155BatchReceived.selector; - } + function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) + external + pure + returns (bytes4) + { + return this.onERC1155BatchReceived.selector; + } - function supportsInterface(bytes4 interfaceId) external pure returns (bool) { - return interfaceId == type(IERC1155Receiver).interfaceId || interfaceId == type(IERC721Receiver).interfaceId - || interfaceId == type(IERC165).interfaceId; - } + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId || interfaceId == type(IERC721Receiver).interfaceId + || interfaceId == type(IERC165).interfaceId; + } } diff --git a/src/aggregate-router/base/Dispatcher.sol b/src/aggregate-router/base/Dispatcher.sol index 5c1f5f1..d8e9ee7 100644 --- a/src/aggregate-router/base/Dispatcher.sol +++ b/src/aggregate-router/base/Dispatcher.sol @@ -1,427 +1,426 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {V2SwapRouter} from '../modules/uniswap/v2/V2SwapRouter.sol'; -import {V3SwapRouter} from '../modules/uniswap/v3/V3SwapRouter.sol'; -import {BytesLib} from '../modules/uniswap/v3/BytesLib.sol'; -import {Payments} from '../modules/Payments.sol'; -import {PaymentsImmutables} from '../modules/PaymentsImmutables.sol'; -import {NFTImmutables} from '../modules/NFTImmutables.sol'; -import {Callbacks} from '../base/Callbacks.sol'; -import {Commands} from '../libraries/Commands.sol'; -import {LockAndMsgSender} from './LockAndMsgSender.sol'; -import {ERC721} from 'solmate/src/tokens/ERC721.sol'; -import {ERC1155} from 'solmate/src/tokens/ERC1155.sol'; -import {ERC20} from 'solmate/src/tokens/ERC20.sol'; -import {IAllowanceTransfer} from 'permit2/src/interfaces/IAllowanceTransfer.sol'; -import {ICryptoPunksMarket} from '../interfaces/external/ICryptoPunksMarket.sol'; +import { V2SwapRouter } from "../modules/uniswap/v2/V2SwapRouter.sol"; +import { V3SwapRouter } from "../modules/uniswap/v3/V3SwapRouter.sol"; +import { BytesLib } from "../modules/uniswap/v3/BytesLib.sol"; +import { Payments } from "../modules/Payments.sol"; +import { PaymentsImmutables } from "../modules/PaymentsImmutables.sol"; +import { NFTImmutables } from "../modules/NFTImmutables.sol"; +import { Callbacks } from "../base/Callbacks.sol"; +import { Commands } from "../libraries/Commands.sol"; +import { LockAndMsgSender } from "./LockAndMsgSender.sol"; +import { ERC721 } from "solmate/src/tokens/ERC721.sol"; +import { ERC1155 } from "solmate/src/tokens/ERC1155.sol"; +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; +import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import { ICryptoPunksMarket } from "../interfaces/external/ICryptoPunksMarket.sol"; /// @title Decodes and Executes Commands /// @notice Called by the UniversalRouter contract to efficiently decode and execute a singular command abstract contract Dispatcher is NFTImmutables, Payments, V2SwapRouter, V3SwapRouter, Callbacks, LockAndMsgSender { - using BytesLib for bytes; + using BytesLib for bytes; - error InvalidCommandType(uint256 commandType); - error BuyPunkFailed(); - error InvalidOwnerERC721(); - error InvalidOwnerERC1155(); - error BalanceTooLow(); + error InvalidCommandType(uint256 commandType); + error BuyPunkFailed(); + error InvalidOwnerERC721(); + error InvalidOwnerERC1155(); + error BalanceTooLow(); - /// @notice Decodes and executes the given command with the given inputs - /// @param commandType The command type to execute - /// @param inputs The inputs to execute the command with - /// @dev 2 masks are used to enable use of a nested-if statement in execution for efficiency reasons - /// @return success True on success of the command, false on failure - /// @return output The outputs or error messages, if any, from the command - function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) { - uint256 command = uint8(commandType & Commands.COMMAND_TYPE_MASK); + /// @notice Decodes and executes the given command with the given inputs + /// @param commandType The command type to execute + /// @param inputs The inputs to execute the command with + /// @dev 2 masks are used to enable use of a nested-if statement in execution for efficiency reasons + /// @return success True on success of the command, false on failure + /// @return output The outputs or error messages, if any, from the command + function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) { + uint256 command = uint8(commandType & Commands.COMMAND_TYPE_MASK); - success = true; + success = true; - if (command < Commands.FOURTH_IF_BOUNDARY) { - if (command < Commands.SECOND_IF_BOUNDARY) { - // 0x00 <= command < 0x08 - if (command < Commands.FIRST_IF_BOUNDARY) { - if (command == Commands.V3_SWAP_EXACT_IN) { - // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) - address recipient; - uint256 amountIn; - uint256 amountOutMin; - bool payerIsUser; - assembly { - recipient := calldataload(inputs.offset) - amountIn := calldataload(add(inputs.offset, 0x20)) - amountOutMin := calldataload(add(inputs.offset, 0x40)) - // 0x60 offset is the path, decoded below - payerIsUser := calldataload(add(inputs.offset, 0x80)) - } - bytes calldata path = inputs.toBytes(3); - address payer = payerIsUser ? lockedBy : address(this); - v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer); - } else if (command == Commands.V3_SWAP_EXACT_OUT) { - // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) - address recipient; - uint256 amountOut; - uint256 amountInMax; - bool payerIsUser; - assembly { - recipient := calldataload(inputs.offset) - amountOut := calldataload(add(inputs.offset, 0x20)) - amountInMax := calldataload(add(inputs.offset, 0x40)) - // 0x60 offset is the path, decoded below - payerIsUser := calldataload(add(inputs.offset, 0x80)) - } - bytes calldata path = inputs.toBytes(3); - address payer = payerIsUser ? lockedBy : address(this); - v3SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer); - } else if (command == Commands.PERMIT2_TRANSFER_FROM) { - // equivalent: abi.decode(inputs, (address, address, uint160)) - address token; - address recipient; - uint160 amount; - assembly { - token := calldataload(inputs.offset) - recipient := calldataload(add(inputs.offset, 0x20)) - amount := calldataload(add(inputs.offset, 0x40)) - } - permit2TransferFrom(token, lockedBy, map(recipient), amount); - } else if (command == Commands.PERMIT2_PERMIT_BATCH) { - (IAllowanceTransfer.PermitBatch memory permitBatch,) = - abi.decode(inputs, (IAllowanceTransfer.PermitBatch, bytes)); - bytes calldata data = inputs.toBytes(1); - PERMIT2.permit(lockedBy, permitBatch, data); - } else if (command == Commands.SWEEP) { - // equivalent: abi.decode(inputs, (address, address, uint256)) - address token; - address recipient; - uint160 amountMin; - assembly { - token := calldataload(inputs.offset) - recipient := calldataload(add(inputs.offset, 0x20)) - amountMin := calldataload(add(inputs.offset, 0x40)) - } - Payments.sweep(token, map(recipient), amountMin); - } else if (command == Commands.TRANSFER) { - // equivalent: abi.decode(inputs, (address, address, uint256)) - address token; - address recipient; - uint256 value; - assembly { - token := calldataload(inputs.offset) - recipient := calldataload(add(inputs.offset, 0x20)) - value := calldataload(add(inputs.offset, 0x40)) - } - Payments.pay(token, map(recipient), value); - } else if (command == Commands.PAY_PORTION) { - // equivalent: abi.decode(inputs, (address, address, uint256)) - address token; - address recipient; - uint256 bips; - assembly { - token := calldataload(inputs.offset) - recipient := calldataload(add(inputs.offset, 0x20)) - bips := calldataload(add(inputs.offset, 0x40)) - } - Payments.payPortion(token, map(recipient), bips); - } else { - // placeholder area for command 0x07 - revert InvalidCommandType(command); - } - // 0x08 <= command < 0x10 - } else { - if (command == Commands.V2_SWAP_EXACT_IN) { - // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) - address recipient; - uint256 amountIn; - uint256 amountOutMin; - bool payerIsUser; - assembly { - recipient := calldataload(inputs.offset) - amountIn := calldataload(add(inputs.offset, 0x20)) - amountOutMin := calldataload(add(inputs.offset, 0x40)) - // 0x60 offset is the path, decoded below - payerIsUser := calldataload(add(inputs.offset, 0x80)) - } - address[] calldata path = inputs.toAddressArray(3); - address payer = payerIsUser ? lockedBy : address(this); - v2SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer); - } else if (command == Commands.V2_SWAP_EXACT_OUT) { - // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) - address recipient; - uint256 amountOut; - uint256 amountInMax; - bool payerIsUser; - assembly { - recipient := calldataload(inputs.offset) - amountOut := calldataload(add(inputs.offset, 0x20)) - amountInMax := calldataload(add(inputs.offset, 0x40)) - // 0x60 offset is the path, decoded below - payerIsUser := calldataload(add(inputs.offset, 0x80)) - } - address[] calldata path = inputs.toAddressArray(3); - address payer = payerIsUser ? lockedBy : address(this); - v2SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer); - } else if (command == Commands.PERMIT2_PERMIT) { - // equivalent: abi.decode(inputs, (IAllowanceTransfer.PermitSingle, bytes)) - IAllowanceTransfer.PermitSingle calldata permitSingle; - assembly { - permitSingle := inputs.offset - } - bytes calldata data = inputs.toBytes(6); // PermitSingle takes first 6 slots (0..5) - PERMIT2.permit(lockedBy, permitSingle, data); - } else if (command == Commands.WRAP_ETH) { - // equivalent: abi.decode(inputs, (address, uint256)) - address recipient; - uint256 amountMin; - assembly { - recipient := calldataload(inputs.offset) - amountMin := calldataload(add(inputs.offset, 0x20)) - } - Payments.wrapETH(map(recipient), amountMin); - } else if (command == Commands.UNWRAP_WETH) { - // equivalent: abi.decode(inputs, (address, uint256)) - address recipient; - uint256 amountMin; - assembly { - recipient := calldataload(inputs.offset) - amountMin := calldataload(add(inputs.offset, 0x20)) - } - Payments.unwrapWETH9(map(recipient), amountMin); - } else if (command == Commands.PERMIT2_TRANSFER_FROM_BATCH) { - (IAllowanceTransfer.AllowanceTransferDetails[] memory batchDetails) = - abi.decode(inputs, (IAllowanceTransfer.AllowanceTransferDetails[])); - permit2TransferFrom(batchDetails, lockedBy); - } else if (command == Commands.BALANCE_CHECK_ERC20) { - // equivalent: abi.decode(inputs, (address, address, uint256)) - address owner; - address token; - uint256 minBalance; - assembly { - owner := calldataload(inputs.offset) - token := calldataload(add(inputs.offset, 0x20)) - minBalance := calldataload(add(inputs.offset, 0x40)) - } - success = (ERC20(token).balanceOf(owner) >= minBalance); - if (!success) output = abi.encodePacked(BalanceTooLow.selector); - } else { - // placeholder area for command 0x0f - revert InvalidCommandType(command); - } - } - // 0x10 <= command - } else { - // 0x10 <= command < 0x18 - if (command < Commands.THIRD_IF_BOUNDARY) { - if (command == Commands.SEAPORT_V1_5) { - /// @dev Seaport 1.4 and 1.5 allow for orders to be created by contracts. - /// These orders pass control to the contract offerers during fufillment, - /// allowing them to perform any number of destructive actions as a holder of the NFT. - /// Integrators should be aware that in some scenarios: e.g. purchasing an NFT that allows the holder - /// to claim another NFT, the contract offerer can "steal" the claim during order fufillment. - /// For some such purchases, an OWNER_CHECK command can be prepended to ensure that all tokens have the desired owner at the end of the transaction. - /// This is also outlined in the Seaport documentation: https://github.com/ProjectOpenSea/seaport/blob/main/docs/SeaportDocumentation.md - (uint256 value, bytes calldata data) = getValueAndData(inputs); - (success, output) = SEAPORT_V1_5.call{value: value}(data); - } else if (command == Commands.LOOKS_RARE_V2) { - // equivalent: abi.decode(inputs, (uint256, bytes)) - uint256 value; - assembly { - value := calldataload(inputs.offset) - } - bytes calldata data = inputs.toBytes(1); - (success, output) = LOOKS_RARE_V2.call{value: value}(data); - } else if (command == Commands.NFTX) { - // equivalent: abi.decode(inputs, (uint256, bytes)) - (uint256 value, bytes calldata data) = getValueAndData(inputs); - (success, output) = NFTX_ZAP.call{value: value}(data); - } else if (command == Commands.CRYPTOPUNKS) { - // equivalent: abi.decode(inputs, (uint256, address, uint256)) - uint256 punkId; - address recipient; - uint256 value; - assembly { - punkId := calldataload(inputs.offset) - recipient := calldataload(add(inputs.offset, 0x20)) - value := calldataload(add(inputs.offset, 0x40)) - } - (success, output) = CRYPTOPUNKS.call{value: value}( - abi.encodeWithSelector(ICryptoPunksMarket.buyPunk.selector, punkId) - ); - if (success) ICryptoPunksMarket(CRYPTOPUNKS).transferPunk(map(recipient), punkId); - else output = abi.encodePacked(BuyPunkFailed.selector); - } else if (command == Commands.OWNER_CHECK_721) { - // equivalent: abi.decode(inputs, (address, address, uint256)) - address owner; - address token; - uint256 id; - assembly { - owner := calldataload(inputs.offset) - token := calldataload(add(inputs.offset, 0x20)) - id := calldataload(add(inputs.offset, 0x40)) - } - success = (ERC721(token).ownerOf(id) == owner); - if (!success) output = abi.encodePacked(InvalidOwnerERC721.selector); - } else if (command == Commands.OWNER_CHECK_1155) { - // equivalent: abi.decode(inputs, (address, address, uint256, uint256)) - address owner; - address token; - uint256 id; - uint256 minBalance; - assembly { - owner := calldataload(inputs.offset) - token := calldataload(add(inputs.offset, 0x20)) - id := calldataload(add(inputs.offset, 0x40)) - minBalance := calldataload(add(inputs.offset, 0x60)) - } - success = (ERC1155(token).balanceOf(owner, id) >= minBalance); - if (!success) output = abi.encodePacked(InvalidOwnerERC1155.selector); - } else if (command == Commands.SWEEP_ERC721) { - // equivalent: abi.decode(inputs, (address, address, uint256)) - address token; - address recipient; - uint256 id; - assembly { - token := calldataload(inputs.offset) - recipient := calldataload(add(inputs.offset, 0x20)) - id := calldataload(add(inputs.offset, 0x40)) - } - Payments.sweepERC721(token, map(recipient), id); - } - // 0x18 <= command < 0x1f - } else { - if (command == Commands.X2Y2_721) { - (success, output) = callAndTransfer721(inputs, X2Y2); - } else if (command == Commands.SUDOSWAP) { - // equivalent: abi.decode(inputs, (uint256, bytes)) - (uint256 value, bytes calldata data) = getValueAndData(inputs); - (success, output) = SUDOSWAP.call{value: value}(data); - } else if (command == Commands.NFT20) { - // equivalent: abi.decode(inputs, (uint256, bytes)) - (uint256 value, bytes calldata data) = getValueAndData(inputs); - (success, output) = NFT20_ZAP.call{value: value}(data); - } else if (command == Commands.X2Y2_1155) { - (success, output) = callAndTransfer1155(inputs, X2Y2); - } else if (command == Commands.FOUNDATION) { - (success, output) = callAndTransfer721(inputs, FOUNDATION); - } else if (command == Commands.SWEEP_ERC1155) { - // equivalent: abi.decode(inputs, (address, address, uint256, uint256)) - address token; - address recipient; - uint256 id; - uint256 amount; - assembly { - token := calldataload(inputs.offset) - recipient := calldataload(add(inputs.offset, 0x20)) - id := calldataload(add(inputs.offset, 0x40)) - amount := calldataload(add(inputs.offset, 0x60)) - } - Payments.sweepERC1155(token, map(recipient), id, amount); - } else if (command == Commands.ELEMENT_MARKET) { - // equivalent: abi.decode(inputs, (uint256, bytes)) - (uint256 value, bytes calldata data) = getValueAndData(inputs); - (success, output) = ELEMENT_MARKET.call{value: value}(data); - } else { - // placeholder for command 0x1f - revert InvalidCommandType(command); - } - } + if (command < Commands.FOURTH_IF_BOUNDARY) { + if (command < Commands.SECOND_IF_BOUNDARY) { + // 0x00 <= command < 0x08 + if (command < Commands.FIRST_IF_BOUNDARY) { + if (command == Commands.V3_SWAP_EXACT_IN) { + // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) + address recipient; + uint256 amountIn; + uint256 amountOutMin; + bool payerIsUser; + assembly { + recipient := calldataload(inputs.offset) + amountIn := calldataload(add(inputs.offset, 0x20)) + amountOutMin := calldataload(add(inputs.offset, 0x40)) + // 0x60 offset is the path, decoded below + payerIsUser := calldataload(add(inputs.offset, 0x80)) } - // 0x20 <= command + bytes calldata path = inputs.toBytes(3); + address payer = payerIsUser ? lockedBy : address(this); + v3SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer); + } else if (command == Commands.V3_SWAP_EXACT_OUT) { + // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) + address recipient; + uint256 amountOut; + uint256 amountInMax; + bool payerIsUser; + assembly { + recipient := calldataload(inputs.offset) + amountOut := calldataload(add(inputs.offset, 0x20)) + amountInMax := calldataload(add(inputs.offset, 0x40)) + // 0x60 offset is the path, decoded below + payerIsUser := calldataload(add(inputs.offset, 0x80)) + } + bytes calldata path = inputs.toBytes(3); + address payer = payerIsUser ? lockedBy : address(this); + v3SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer); + } else if (command == Commands.PERMIT2_TRANSFER_FROM) { + // equivalent: abi.decode(inputs, (address, address, uint160)) + address token; + address recipient; + uint160 amount; + assembly { + token := calldataload(inputs.offset) + recipient := calldataload(add(inputs.offset, 0x20)) + amount := calldataload(add(inputs.offset, 0x40)) + } + permit2TransferFrom(token, lockedBy, map(recipient), amount); + } else if (command == Commands.PERMIT2_PERMIT_BATCH) { + (IAllowanceTransfer.PermitBatch memory permitBatch,) = + abi.decode(inputs, (IAllowanceTransfer.PermitBatch, bytes)); + bytes calldata data = inputs.toBytes(1); + PERMIT2.permit(lockedBy, permitBatch, data); + } else if (command == Commands.SWEEP) { + // equivalent: abi.decode(inputs, (address, address, uint256)) + address token; + address recipient; + uint160 amountMin; + assembly { + token := calldataload(inputs.offset) + recipient := calldataload(add(inputs.offset, 0x20)) + amountMin := calldataload(add(inputs.offset, 0x40)) + } + Payments.sweep(token, map(recipient), amountMin); + } else if (command == Commands.TRANSFER) { + // equivalent: abi.decode(inputs, (address, address, uint256)) + address token; + address recipient; + uint256 value; + assembly { + token := calldataload(inputs.offset) + recipient := calldataload(add(inputs.offset, 0x20)) + value := calldataload(add(inputs.offset, 0x40)) + } + Payments.pay(token, map(recipient), value); + } else if (command == Commands.PAY_PORTION) { + // equivalent: abi.decode(inputs, (address, address, uint256)) + address token; + address recipient; + uint256 bips; + assembly { + token := calldataload(inputs.offset) + recipient := calldataload(add(inputs.offset, 0x20)) + bips := calldataload(add(inputs.offset, 0x40)) + } + Payments.payPortion(token, map(recipient), bips); + } else { + // placeholder area for command 0x07 + revert InvalidCommandType(command); + } + // 0x08 <= command < 0x10 } else { - if (command == Commands.SEAPORT_V1_4) { - /// @dev Seaport 1.4 and 1.5 allow for orders to be created by contracts. - /// These orders pass control to the contract offerers during fufillment, - /// allowing them to perform any number of destructive actions as a holder of the NFT. - /// Integrators should be aware that in some scenarios: e.g. purchasing an NFT that allows the holder - /// to claim another NFT, the contract offerer can "steal" the claim during order fufillment. - /// For some such purchases, an OWNER_CHECK command can be prepended to ensure that all tokens have the desired owner at the end of the transaction. - /// This is also outlined in the Seaport documentation: https://github.com/ProjectOpenSea/seaport/blob/main/docs/SeaportDocumentation.md - (uint256 value, bytes calldata data) = getValueAndData(inputs); - (success, output) = SEAPORT_V1_4.call{value: value}(data); - } else if (command == Commands.EXECUTE_SUB_PLAN) { - bytes calldata _commands = inputs.toBytes(0); - bytes[] calldata _inputs = inputs.toBytesArray(1); - (success, output) = - (address(this)).call(abi.encodeWithSelector(Dispatcher.execute.selector, _commands, _inputs)); - } else if (command == Commands.APPROVE_ERC20) { - ERC20 token; - PaymentsImmutables.Spenders spender; - assembly { - token := calldataload(inputs.offset) - spender := calldataload(add(inputs.offset, 0x20)) - } - Payments.approveERC20(token, spender); - } else { - // placeholder area for commands 0x23-0x3f - revert InvalidCommandType(command); + if (command == Commands.V2_SWAP_EXACT_IN) { + // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) + address recipient; + uint256 amountIn; + uint256 amountOutMin; + bool payerIsUser; + assembly { + recipient := calldataload(inputs.offset) + amountIn := calldataload(add(inputs.offset, 0x20)) + amountOutMin := calldataload(add(inputs.offset, 0x40)) + // 0x60 offset is the path, decoded below + payerIsUser := calldataload(add(inputs.offset, 0x80)) + } + address[] calldata path = inputs.toAddressArray(3); + address payer = payerIsUser ? lockedBy : address(this); + v2SwapExactInput(map(recipient), amountIn, amountOutMin, path, payer); + } else if (command == Commands.V2_SWAP_EXACT_OUT) { + // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool)) + address recipient; + uint256 amountOut; + uint256 amountInMax; + bool payerIsUser; + assembly { + recipient := calldataload(inputs.offset) + amountOut := calldataload(add(inputs.offset, 0x20)) + amountInMax := calldataload(add(inputs.offset, 0x40)) + // 0x60 offset is the path, decoded below + payerIsUser := calldataload(add(inputs.offset, 0x80)) + } + address[] calldata path = inputs.toAddressArray(3); + address payer = payerIsUser ? lockedBy : address(this); + v2SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer); + } else if (command == Commands.PERMIT2_PERMIT) { + // equivalent: abi.decode(inputs, (IAllowanceTransfer.PermitSingle, bytes)) + IAllowanceTransfer.PermitSingle calldata permitSingle; + assembly { + permitSingle := inputs.offset + } + bytes calldata data = inputs.toBytes(6); // PermitSingle takes first 6 slots (0..5) + PERMIT2.permit(lockedBy, permitSingle, data); + } else if (command == Commands.WRAP_ETH) { + // equivalent: abi.decode(inputs, (address, uint256)) + address recipient; + uint256 amountMin; + assembly { + recipient := calldataload(inputs.offset) + amountMin := calldataload(add(inputs.offset, 0x20)) } + Payments.wrapETH(map(recipient), amountMin); + } else if (command == Commands.UNWRAP_WETH) { + // equivalent: abi.decode(inputs, (address, uint256)) + address recipient; + uint256 amountMin; + assembly { + recipient := calldataload(inputs.offset) + amountMin := calldataload(add(inputs.offset, 0x20)) + } + Payments.unwrapWETH9(map(recipient), amountMin); + } else if (command == Commands.PERMIT2_TRANSFER_FROM_BATCH) { + (IAllowanceTransfer.AllowanceTransferDetails[] memory batchDetails) = + abi.decode(inputs, (IAllowanceTransfer.AllowanceTransferDetails[])); + permit2TransferFrom(batchDetails, lockedBy); + } else if (command == Commands.BALANCE_CHECK_ERC20) { + // equivalent: abi.decode(inputs, (address, address, uint256)) + address owner; + address token; + uint256 minBalance; + assembly { + owner := calldataload(inputs.offset) + token := calldataload(add(inputs.offset, 0x20)) + minBalance := calldataload(add(inputs.offset, 0x40)) + } + success = (ERC20(token).balanceOf(owner) >= minBalance); + if (!success) output = abi.encodePacked(BalanceTooLow.selector); + } else { + // placeholder area for command 0x0f + revert InvalidCommandType(command); + } } - } - - /// @notice Executes encoded commands along with provided inputs. - /// @param commands A set of concatenated commands, each 1 byte in length - /// @param inputs An array of byte strings containing abi encoded inputs for each command - function execute(bytes calldata commands, bytes[] calldata inputs) external payable virtual; - - /// @notice Performs a call to purchase an ERC721, then transfers the ERC721 to a specified recipient - /// @param inputs The inputs for the protocol and ERC721 transfer, encoded - /// @param protocol The protocol to pass the calldata to - /// @return success True on success of the command, false on failure - /// @return output The outputs or error messages, if any, from the command - function callAndTransfer721(bytes calldata inputs, address protocol) - internal - returns (bool success, bytes memory output) - { - // equivalent: abi.decode(inputs, (uint256, bytes, address, address, uint256)) + // 0x10 <= command + } else { + // 0x10 <= command < 0x18 + if (command < Commands.THIRD_IF_BOUNDARY) { + if (command == Commands.SEAPORT_V1_5) { + /// @dev Seaport 1.4 and 1.5 allow for orders to be created by contracts. + /// These orders pass control to the contract offerers during fufillment, + /// allowing them to perform any number of destructive actions as a holder of the NFT. + /// Integrators should be aware that in some scenarios: e.g. purchasing an NFT that allows the holder + /// to claim another NFT, the contract offerer can "steal" the claim during order fufillment. + /// For some such purchases, an OWNER_CHECK command can be prepended to ensure that all tokens have the desired owner at the end of the transaction. + /// This is also outlined in the Seaport documentation: https://github.com/ProjectOpenSea/seaport/blob/main/docs/SeaportDocumentation.md + (uint256 value, bytes calldata data) = getValueAndData(inputs); + (success, output) = SEAPORT_V1_5.call{ value: value }(data); + } else if (command == Commands.LOOKS_RARE_V2) { + // equivalent: abi.decode(inputs, (uint256, bytes)) + uint256 value; + assembly { + value := calldataload(inputs.offset) + } + bytes calldata data = inputs.toBytes(1); + (success, output) = LOOKS_RARE_V2.call{ value: value }(data); + } else if (command == Commands.NFTX) { + // equivalent: abi.decode(inputs, (uint256, bytes)) + (uint256 value, bytes calldata data) = getValueAndData(inputs); + (success, output) = NFTX_ZAP.call{ value: value }(data); + } else if (command == Commands.CRYPTOPUNKS) { + // equivalent: abi.decode(inputs, (uint256, address, uint256)) + uint256 punkId; + address recipient; + uint256 value; + assembly { + punkId := calldataload(inputs.offset) + recipient := calldataload(add(inputs.offset, 0x20)) + value := calldataload(add(inputs.offset, 0x40)) + } + (success, output) = + CRYPTOPUNKS.call{ value: value }(abi.encodeWithSelector(ICryptoPunksMarket.buyPunk.selector, punkId)); + if (success) ICryptoPunksMarket(CRYPTOPUNKS).transferPunk(map(recipient), punkId); + else output = abi.encodePacked(BuyPunkFailed.selector); + } else if (command == Commands.OWNER_CHECK_721) { + // equivalent: abi.decode(inputs, (address, address, uint256)) + address owner; + address token; + uint256 id; + assembly { + owner := calldataload(inputs.offset) + token := calldataload(add(inputs.offset, 0x20)) + id := calldataload(add(inputs.offset, 0x40)) + } + success = (ERC721(token).ownerOf(id) == owner); + if (!success) output = abi.encodePacked(InvalidOwnerERC721.selector); + } else if (command == Commands.OWNER_CHECK_1155) { + // equivalent: abi.decode(inputs, (address, address, uint256, uint256)) + address owner; + address token; + uint256 id; + uint256 minBalance; + assembly { + owner := calldataload(inputs.offset) + token := calldataload(add(inputs.offset, 0x20)) + id := calldataload(add(inputs.offset, 0x40)) + minBalance := calldataload(add(inputs.offset, 0x60)) + } + success = (ERC1155(token).balanceOf(owner, id) >= minBalance); + if (!success) output = abi.encodePacked(InvalidOwnerERC1155.selector); + } else if (command == Commands.SWEEP_ERC721) { + // equivalent: abi.decode(inputs, (address, address, uint256)) + address token; + address recipient; + uint256 id; + assembly { + token := calldataload(inputs.offset) + recipient := calldataload(add(inputs.offset, 0x20)) + id := calldataload(add(inputs.offset, 0x40)) + } + Payments.sweepERC721(token, map(recipient), id); + } + // 0x18 <= command < 0x1f + } else { + if (command == Commands.X2Y2_721) { + (success, output) = callAndTransfer721(inputs, X2Y2); + } else if (command == Commands.SUDOSWAP) { + // equivalent: abi.decode(inputs, (uint256, bytes)) + (uint256 value, bytes calldata data) = getValueAndData(inputs); + (success, output) = SUDOSWAP.call{ value: value }(data); + } else if (command == Commands.NFT20) { + // equivalent: abi.decode(inputs, (uint256, bytes)) + (uint256 value, bytes calldata data) = getValueAndData(inputs); + (success, output) = NFT20_ZAP.call{ value: value }(data); + } else if (command == Commands.X2Y2_1155) { + (success, output) = callAndTransfer1155(inputs, X2Y2); + } else if (command == Commands.FOUNDATION) { + (success, output) = callAndTransfer721(inputs, FOUNDATION); + } else if (command == Commands.SWEEP_ERC1155) { + // equivalent: abi.decode(inputs, (address, address, uint256, uint256)) + address token; + address recipient; + uint256 id; + uint256 amount; + assembly { + token := calldataload(inputs.offset) + recipient := calldataload(add(inputs.offset, 0x20)) + id := calldataload(add(inputs.offset, 0x40)) + amount := calldataload(add(inputs.offset, 0x60)) + } + Payments.sweepERC1155(token, map(recipient), id, amount); + } else if (command == Commands.ELEMENT_MARKET) { + // equivalent: abi.decode(inputs, (uint256, bytes)) + (uint256 value, bytes calldata data) = getValueAndData(inputs); + (success, output) = ELEMENT_MARKET.call{ value: value }(data); + } else { + // placeholder for command 0x1f + revert InvalidCommandType(command); + } + } + } + // 0x20 <= command + } else { + if (command == Commands.SEAPORT_V1_4) { + /// @dev Seaport 1.4 and 1.5 allow for orders to be created by contracts. + /// These orders pass control to the contract offerers during fufillment, + /// allowing them to perform any number of destructive actions as a holder of the NFT. + /// Integrators should be aware that in some scenarios: e.g. purchasing an NFT that allows the holder + /// to claim another NFT, the contract offerer can "steal" the claim during order fufillment. + /// For some such purchases, an OWNER_CHECK command can be prepended to ensure that all tokens have the desired owner at the end of the transaction. + /// This is also outlined in the Seaport documentation: https://github.com/ProjectOpenSea/seaport/blob/main/docs/SeaportDocumentation.md (uint256 value, bytes calldata data) = getValueAndData(inputs); - address recipient; - address token; - uint256 id; + (success, output) = SEAPORT_V1_4.call{ value: value }(data); + } else if (command == Commands.EXECUTE_SUB_PLAN) { + bytes calldata _commands = inputs.toBytes(0); + bytes[] calldata _inputs = inputs.toBytesArray(1); + (success, output) = + (address(this)).call(abi.encodeWithSelector(Dispatcher.execute.selector, _commands, _inputs)); + } else if (command == Commands.APPROVE_ERC20) { + ERC20 token; + PaymentsImmutables.Spenders spender; assembly { - // 0x00 and 0x20 offsets are value and data, above - recipient := calldataload(add(inputs.offset, 0x40)) - token := calldataload(add(inputs.offset, 0x60)) - id := calldataload(add(inputs.offset, 0x80)) + token := calldataload(inputs.offset) + spender := calldataload(add(inputs.offset, 0x20)) } - (success, output) = protocol.call{value: value}(data); - if (success) ERC721(token).safeTransferFrom(address(this), map(recipient), id); + Payments.approveERC20(token, spender); + } else { + // placeholder area for commands 0x23-0x3f + revert InvalidCommandType(command); + } } + } - /// @notice Performs a call to purchase an ERC1155, then transfers the ERC1155 to a specified recipient - /// @param inputs The inputs for the protocol and ERC1155 transfer, encoded - /// @param protocol The protocol to pass the calldata to - /// @return success True on success of the command, false on failure - /// @return output The outputs or error messages, if any, from the command - function callAndTransfer1155(bytes calldata inputs, address protocol) - internal - returns (bool success, bytes memory output) - { - // equivalent: abi.decode(inputs, (uint256, bytes, address, address, uint256, uint256)) - (uint256 value, bytes calldata data) = getValueAndData(inputs); - address recipient; - address token; - uint256 id; - uint256 amount; - assembly { - // 0x00 and 0x20 offsets are value and data, above - recipient := calldataload(add(inputs.offset, 0x40)) - token := calldataload(add(inputs.offset, 0x60)) - id := calldataload(add(inputs.offset, 0x80)) - amount := calldataload(add(inputs.offset, 0xa0)) - } - (success, output) = protocol.call{value: value}(data); - if (success) ERC1155(token).safeTransferFrom(address(this), map(recipient), id, amount, new bytes(0)); + /// @notice Executes encoded commands along with provided inputs. + /// @param commands A set of concatenated commands, each 1 byte in length + /// @param inputs An array of byte strings containing abi encoded inputs for each command + function execute(bytes calldata commands, bytes[] calldata inputs) external payable virtual; + + /// @notice Performs a call to purchase an ERC721, then transfers the ERC721 to a specified recipient + /// @param inputs The inputs for the protocol and ERC721 transfer, encoded + /// @param protocol The protocol to pass the calldata to + /// @return success True on success of the command, false on failure + /// @return output The outputs or error messages, if any, from the command + function callAndTransfer721(bytes calldata inputs, address protocol) + internal + returns (bool success, bytes memory output) + { + // equivalent: abi.decode(inputs, (uint256, bytes, address, address, uint256)) + (uint256 value, bytes calldata data) = getValueAndData(inputs); + address recipient; + address token; + uint256 id; + assembly { + // 0x00 and 0x20 offsets are value and data, above + recipient := calldataload(add(inputs.offset, 0x40)) + token := calldataload(add(inputs.offset, 0x60)) + id := calldataload(add(inputs.offset, 0x80)) } + (success, output) = protocol.call{ value: value }(data); + if (success) ERC721(token).safeTransferFrom(address(this), map(recipient), id); + } - /// @notice Helper function to extract `value` and `data` parameters from input bytes string - /// @dev The helper assumes that `value` is the first parameter, and `data` is the second - /// @param inputs The bytes string beginning with value and data parameters - /// @return value The 256 bit integer value - /// @return data The data bytes string - function getValueAndData(bytes calldata inputs) internal pure returns (uint256 value, bytes calldata data) { - assembly { - value := calldataload(inputs.offset) - } - data = inputs.toBytes(1); + /// @notice Performs a call to purchase an ERC1155, then transfers the ERC1155 to a specified recipient + /// @param inputs The inputs for the protocol and ERC1155 transfer, encoded + /// @param protocol The protocol to pass the calldata to + /// @return success True on success of the command, false on failure + /// @return output The outputs or error messages, if any, from the command + function callAndTransfer1155(bytes calldata inputs, address protocol) + internal + returns (bool success, bytes memory output) + { + // equivalent: abi.decode(inputs, (uint256, bytes, address, address, uint256, uint256)) + (uint256 value, bytes calldata data) = getValueAndData(inputs); + address recipient; + address token; + uint256 id; + uint256 amount; + assembly { + // 0x00 and 0x20 offsets are value and data, above + recipient := calldataload(add(inputs.offset, 0x40)) + token := calldataload(add(inputs.offset, 0x60)) + id := calldataload(add(inputs.offset, 0x80)) + amount := calldataload(add(inputs.offset, 0xa0)) + } + (success, output) = protocol.call{ value: value }(data); + if (success) ERC1155(token).safeTransferFrom(address(this), map(recipient), id, amount, new bytes(0)); + } + + /// @notice Helper function to extract `value` and `data` parameters from input bytes string + /// @dev The helper assumes that `value` is the first parameter, and `data` is the second + /// @param inputs The bytes string beginning with value and data parameters + /// @return value The 256 bit integer value + /// @return data The data bytes string + function getValueAndData(bytes calldata inputs) internal pure returns (uint256 value, bytes calldata data) { + assembly { + value := calldataload(inputs.offset) } + data = inputs.toBytes(1); + } } diff --git a/src/aggregate-router/base/LockAndMsgSender.sol b/src/aggregate-router/base/LockAndMsgSender.sol index 60e8bb0..ceeb614 100644 --- a/src/aggregate-router/base/LockAndMsgSender.sol +++ b/src/aggregate-router/base/LockAndMsgSender.sol @@ -1,35 +1,35 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {Constants} from '../libraries/Constants.sol'; +import { Constants } from "../libraries/Constants.sol"; contract LockAndMsgSender { - error ContractLocked(); + error ContractLocked(); - address internal constant NOT_LOCKED_FLAG = address(1); - address internal lockedBy = NOT_LOCKED_FLAG; + address internal constant NOT_LOCKED_FLAG = address(1); + address internal lockedBy = NOT_LOCKED_FLAG; - modifier isNotLocked() { - if (msg.sender != address(this)) { - if (lockedBy != NOT_LOCKED_FLAG) revert ContractLocked(); - lockedBy = msg.sender; - _; - lockedBy = NOT_LOCKED_FLAG; - } else { - _; - } + modifier isNotLocked() { + if (msg.sender != address(this)) { + if (lockedBy != NOT_LOCKED_FLAG) revert ContractLocked(); + lockedBy = msg.sender; + _; + lockedBy = NOT_LOCKED_FLAG; + } else { + _; } + } - /// @notice Calculates the recipient address for a command - /// @param recipient The recipient or recipient-flag for the command - /// @return output The resultant recipient for the command - function map(address recipient) internal view returns (address) { - if (recipient == Constants.MSG_SENDER) { - return lockedBy; - } else if (recipient == Constants.ADDRESS_THIS) { - return address(this); - } else { - return recipient; - } + /// @notice Calculates the recipient address for a command + /// @param recipient The recipient or recipient-flag for the command + /// @return output The resultant recipient for the command + function map(address recipient) internal view returns (address) { + if (recipient == Constants.MSG_SENDER) { + return lockedBy; + } else if (recipient == Constants.ADDRESS_THIS) { + return address(this); + } else { + return recipient; } + } } diff --git a/src/aggregate-router/base/RewardsCollector.sol b/src/aggregate-router/base/RewardsCollector.sol index ded3657..d4e5fa7 100644 --- a/src/aggregate-router/base/RewardsCollector.sol +++ b/src/aggregate-router/base/RewardsCollector.sol @@ -1,25 +1,25 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.15; -import {ERC20} from 'solmate/src/tokens/ERC20.sol'; -import {SafeTransferLib} from 'solmate/src/utils/SafeTransferLib.sol'; -import {NFTImmutables} from '../modules/NFTImmutables.sol'; -import {IRewardsCollector} from '../interfaces/IRewardsCollector.sol'; +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; +import { SafeTransferLib } from "solmate/src/utils/SafeTransferLib.sol"; +import { NFTImmutables } from "../modules/NFTImmutables.sol"; +import { IRewardsCollector } from "../interfaces/IRewardsCollector.sol"; abstract contract RewardsCollector is IRewardsCollector, NFTImmutables { - using SafeTransferLib for ERC20; + using SafeTransferLib for ERC20; - event RewardsSent(uint256 amount); + event RewardsSent(uint256 amount); - error UnableToClaim(); + error UnableToClaim(); - /// @inheritdoc IRewardsCollector - function collectRewards(bytes calldata looksRareClaim) external { - (bool success,) = LOOKS_RARE_REWARDS_DISTRIBUTOR.call(looksRareClaim); - if (!success) revert UnableToClaim(); + /// @inheritdoc IRewardsCollector + function collectRewards(bytes calldata looksRareClaim) external { + (bool success,) = LOOKS_RARE_REWARDS_DISTRIBUTOR.call(looksRareClaim); + if (!success) revert UnableToClaim(); - uint256 balance = LOOKS_RARE_TOKEN.balanceOf(address(this)); - LOOKS_RARE_TOKEN.transfer(ROUTER_REWARDS_DISTRIBUTOR, balance); - emit RewardsSent(balance); - } + uint256 balance = LOOKS_RARE_TOKEN.balanceOf(address(this)); + LOOKS_RARE_TOKEN.transfer(ROUTER_REWARDS_DISTRIBUTOR, balance); + emit RewardsSent(balance); + } } diff --git a/src/aggregate-router/base/RouterImmutables.sol b/src/aggregate-router/base/RouterImmutables.sol index 391ce6a..a4669b5 100644 --- a/src/aggregate-router/base/RouterImmutables.sol +++ b/src/aggregate-router/base/RouterImmutables.sol @@ -2,24 +2,24 @@ pragma solidity ^0.8.17; struct RouterParameters { - address permit2; - address weth9; - address seaportV1_5; - address seaportV1_4; - address openseaConduit; - address nftxZap; - address x2y2; - address foundation; - address sudoswap; - address elementMarket; - address nft20Zap; - address cryptopunks; - address looksRareV2; - address routerRewardsDistributor; - address looksRareRewardsDistributor; - address looksRareToken; - address v2Factory; - address v3Factory; - bytes32 pairInitCodeHash; - bytes32 poolInitCodeHash; + address permit2; + address weth9; + address seaportV1_5; + address seaportV1_4; + address openseaConduit; + address nftxZap; + address x2y2; + address foundation; + address sudoswap; + address elementMarket; + address nft20Zap; + address cryptopunks; + address looksRareV2; + address routerRewardsDistributor; + address looksRareRewardsDistributor; + address looksRareToken; + address v2Factory; + address v3Factory; + bytes32 pairInitCodeHash; + bytes32 poolInitCodeHash; } diff --git a/src/aggregate-router/deploy/UnsupportedProtocol.sol b/src/aggregate-router/deploy/UnsupportedProtocol.sol index f55e9a8..e0f5579 100644 --- a/src/aggregate-router/deploy/UnsupportedProtocol.sol +++ b/src/aggregate-router/deploy/UnsupportedProtocol.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.17; /// @title Dummy contract that always reverts /// @notice Used as a placeholder to ensure reverts on attempted calls to protocols unsupported on a given chain contract UnsupportedProtocol { - error UnsupportedProtocolError(); + error UnsupportedProtocolError(); - fallback() external { - revert UnsupportedProtocolError(); - } + fallback() external { + revert UnsupportedProtocolError(); + } } diff --git a/src/aggregate-router/interfaces/IRewardsCollector.sol b/src/aggregate-router/interfaces/IRewardsCollector.sol index e7bd52b..bd217f1 100644 --- a/src/aggregate-router/interfaces/IRewardsCollector.sol +++ b/src/aggregate-router/interfaces/IRewardsCollector.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.15; -import {ERC20} from 'solmate/src/tokens/ERC20.sol'; +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; /// @title LooksRare Rewards Collector /// @notice Implements a permissionless call to fetch LooksRare rewards earned by Universal Router users /// and transfers them to an external rewards distributor contract interface IRewardsCollector { - /// @notice Fetches users' LooksRare rewards and sends them to the distributor contract - /// @param looksRareClaim The data required by LooksRare to claim reward tokens - function collectRewards(bytes calldata looksRareClaim) external; + /// @notice Fetches users' LooksRare rewards and sends them to the distributor contract + /// @param looksRareClaim The data required by LooksRare to claim reward tokens + function collectRewards(bytes calldata looksRareClaim) external; } diff --git a/src/aggregate-router/interfaces/IUniversalRouter.sol b/src/aggregate-router/interfaces/IUniversalRouter.sol index df9fed3..5cb782f 100644 --- a/src/aggregate-router/interfaces/IUniversalRouter.sol +++ b/src/aggregate-router/interfaces/IUniversalRouter.sol @@ -1,26 +1,26 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {IERC721Receiver} from '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; -import {IERC1155Receiver} from '@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol'; -import {IRewardsCollector} from './IRewardsCollector.sol'; +import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import { IRewardsCollector } from "./IRewardsCollector.sol"; interface IUniversalRouter is IRewardsCollector, IERC721Receiver, IERC1155Receiver { - /// @notice Thrown when a required command has failed - error ExecutionFailed(uint256 commandIndex, bytes message); + /// @notice Thrown when a required command has failed + error ExecutionFailed(uint256 commandIndex, bytes message); - /// @notice Thrown when attempting to send ETH directly to the contract - error ETHNotAccepted(); + /// @notice Thrown when attempting to send ETH directly to the contract + error ETHNotAccepted(); - /// @notice Thrown when executing commands with an expired deadline - error TransactionDeadlinePassed(); + /// @notice Thrown when executing commands with an expired deadline + error TransactionDeadlinePassed(); - /// @notice Thrown when attempting to execute commands and an incorrect number of inputs are provided - error LengthMismatch(); + /// @notice Thrown when attempting to execute commands and an incorrect number of inputs are provided + error LengthMismatch(); - /// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired. - /// @param commands A set of concatenated commands, each 1 byte in length - /// @param inputs An array of byte strings containing abi encoded inputs for each command - /// @param deadline The deadline by which the transaction must be executed - function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable; + /// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired. + /// @param commands A set of concatenated commands, each 1 byte in length + /// @param inputs An array of byte strings containing abi encoded inputs for each command + /// @param deadline The deadline by which the transaction must be executed + function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable; } diff --git a/src/aggregate-router/interfaces/external/ICryptoPunksMarket.sol b/src/aggregate-router/interfaces/external/ICryptoPunksMarket.sol index 24e3970..3ddc219 100644 --- a/src/aggregate-router/interfaces/external/ICryptoPunksMarket.sol +++ b/src/aggregate-router/interfaces/external/ICryptoPunksMarket.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.4; /// @title Interface for CryptoPunksMarket interface ICryptoPunksMarket { - /// @notice Buy a cryptopunk - function buyPunk(uint256 punkIndex) external payable; + /// @notice Buy a cryptopunk + function buyPunk(uint256 punkIndex) external payable; - /// @notice Transfer a cryptopunk to another address - function transferPunk(address to, uint256 punkIndex) external; + /// @notice Transfer a cryptopunk to another address + function transferPunk(address to, uint256 punkIndex) external; } diff --git a/src/aggregate-router/interfaces/external/IWETH9.sol b/src/aggregate-router/interfaces/external/IWETH9.sol index 94a5071..b9abdbb 100644 --- a/src/aggregate-router/interfaces/external/IWETH9.sol +++ b/src/aggregate-router/interfaces/external/IWETH9.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.4; -import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @title Interface for WETH9 interface IWETH9 is IERC20 { - /// @notice Deposit ether to get wrapped ether - function deposit() external payable; + /// @notice Deposit ether to get wrapped ether + function deposit() external payable; - /// @notice Withdraw wrapped ether to get ether - function withdraw(uint256) external; + /// @notice Withdraw wrapped ether to get ether + function withdraw(uint256) external; } diff --git a/src/aggregate-router/libraries/Commands.sol b/src/aggregate-router/libraries/Commands.sol index c3bba22..e2d241a 100644 --- a/src/aggregate-router/libraries/Commands.sol +++ b/src/aggregate-router/libraries/Commands.sol @@ -4,71 +4,71 @@ pragma solidity ^0.8.17; /// @title Commands /// @notice Command Flags used to decode commands library Commands { - // Masks to extract certain bits of commands - bytes1 internal constant FLAG_ALLOW_REVERT = 0x80; - bytes1 internal constant COMMAND_TYPE_MASK = 0x3f; + // Masks to extract certain bits of commands + bytes1 internal constant FLAG_ALLOW_REVERT = 0x80; + bytes1 internal constant COMMAND_TYPE_MASK = 0x3f; - // Command Types. Maximum supported command at this moment is 0x3f. + // Command Types. Maximum supported command at this moment is 0x3f. - // Command Types where value<0x08, executed in the first nested-if block - uint256 constant V3_SWAP_EXACT_IN = 0x00; - uint256 constant V3_SWAP_EXACT_OUT = 0x01; - uint256 constant PERMIT2_TRANSFER_FROM = 0x02; - uint256 constant PERMIT2_PERMIT_BATCH = 0x03; - uint256 constant SWEEP = 0x04; - uint256 constant TRANSFER = 0x05; - uint256 constant PAY_PORTION = 0x06; - // COMMAND_PLACEHOLDER = 0x07; + // Command Types where value<0x08, executed in the first nested-if block + uint256 constant V3_SWAP_EXACT_IN = 0x00; + uint256 constant V3_SWAP_EXACT_OUT = 0x01; + uint256 constant PERMIT2_TRANSFER_FROM = 0x02; + uint256 constant PERMIT2_PERMIT_BATCH = 0x03; + uint256 constant SWEEP = 0x04; + uint256 constant TRANSFER = 0x05; + uint256 constant PAY_PORTION = 0x06; + // COMMAND_PLACEHOLDER = 0x07; - // The commands are executed in nested if blocks to minimise gas consumption - // The following constant defines one of the boundaries where the if blocks split commands - uint256 constant FIRST_IF_BOUNDARY = 0x08; + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + uint256 constant FIRST_IF_BOUNDARY = 0x08; - // Command Types where 0x08<=value<=0x0f, executed in the second nested-if block - uint256 constant V2_SWAP_EXACT_IN = 0x08; - uint256 constant V2_SWAP_EXACT_OUT = 0x09; - uint256 constant PERMIT2_PERMIT = 0x0a; - uint256 constant WRAP_ETH = 0x0b; - uint256 constant UNWRAP_WETH = 0x0c; - uint256 constant PERMIT2_TRANSFER_FROM_BATCH = 0x0d; - uint256 constant BALANCE_CHECK_ERC20 = 0x0e; - // COMMAND_PLACEHOLDER = 0x0f; + // Command Types where 0x08<=value<=0x0f, executed in the second nested-if block + uint256 constant V2_SWAP_EXACT_IN = 0x08; + uint256 constant V2_SWAP_EXACT_OUT = 0x09; + uint256 constant PERMIT2_PERMIT = 0x0a; + uint256 constant WRAP_ETH = 0x0b; + uint256 constant UNWRAP_WETH = 0x0c; + uint256 constant PERMIT2_TRANSFER_FROM_BATCH = 0x0d; + uint256 constant BALANCE_CHECK_ERC20 = 0x0e; + // COMMAND_PLACEHOLDER = 0x0f; - // The commands are executed in nested if blocks to minimise gas consumption - // The following constant defines one of the boundaries where the if blocks split commands - uint256 constant SECOND_IF_BOUNDARY = 0x10; + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + uint256 constant SECOND_IF_BOUNDARY = 0x10; - // Command Types where 0x10<=value<0x18, executed in the third nested-if block - uint256 constant SEAPORT_V1_5 = 0x10; - uint256 constant LOOKS_RARE_V2 = 0x11; - uint256 constant NFTX = 0x12; - uint256 constant CRYPTOPUNKS = 0x13; - // 0x14; - uint256 constant OWNER_CHECK_721 = 0x15; - uint256 constant OWNER_CHECK_1155 = 0x16; - uint256 constant SWEEP_ERC721 = 0x17; + // Command Types where 0x10<=value<0x18, executed in the third nested-if block + uint256 constant SEAPORT_V1_5 = 0x10; + uint256 constant LOOKS_RARE_V2 = 0x11; + uint256 constant NFTX = 0x12; + uint256 constant CRYPTOPUNKS = 0x13; + // 0x14; + uint256 constant OWNER_CHECK_721 = 0x15; + uint256 constant OWNER_CHECK_1155 = 0x16; + uint256 constant SWEEP_ERC721 = 0x17; - // The commands are executed in nested if blocks to minimise gas consumption - // The following constant defines one of the boundaries where the if blocks split commands - uint256 constant THIRD_IF_BOUNDARY = 0x18; + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + uint256 constant THIRD_IF_BOUNDARY = 0x18; - // Command Types where 0x18<=value<=0x1f, executed in the final nested-if block - uint256 constant X2Y2_721 = 0x18; - uint256 constant SUDOSWAP = 0x19; - uint256 constant NFT20 = 0x1a; - uint256 constant X2Y2_1155 = 0x1b; - uint256 constant FOUNDATION = 0x1c; - uint256 constant SWEEP_ERC1155 = 0x1d; - uint256 constant ELEMENT_MARKET = 0x1e; - // COMMAND_PLACEHOLDER = 0x1f; + // Command Types where 0x18<=value<=0x1f, executed in the final nested-if block + uint256 constant X2Y2_721 = 0x18; + uint256 constant SUDOSWAP = 0x19; + uint256 constant NFT20 = 0x1a; + uint256 constant X2Y2_1155 = 0x1b; + uint256 constant FOUNDATION = 0x1c; + uint256 constant SWEEP_ERC1155 = 0x1d; + uint256 constant ELEMENT_MARKET = 0x1e; + // COMMAND_PLACEHOLDER = 0x1f; - // The commands are executed in nested if blocks to minimise gas consumption - // The following constant defines one of the boundaries where the if blocks split commands - uint256 constant FOURTH_IF_BOUNDARY = 0x20; + // The commands are executed in nested if blocks to minimise gas consumption + // The following constant defines one of the boundaries where the if blocks split commands + uint256 constant FOURTH_IF_BOUNDARY = 0x20; - // Command Types where 0x20<=value - uint256 constant SEAPORT_V1_4 = 0x20; - uint256 constant EXECUTE_SUB_PLAN = 0x21; - uint256 constant APPROVE_ERC20 = 0x22; - // COMMAND_PLACEHOLDER for 0x23 to 0x3f (all unused) + // Command Types where 0x20<=value + uint256 constant SEAPORT_V1_4 = 0x20; + uint256 constant EXECUTE_SUB_PLAN = 0x21; + uint256 constant APPROVE_ERC20 = 0x22; + // COMMAND_PLACEHOLDER for 0x23 to 0x3f (all unused) } diff --git a/src/aggregate-router/libraries/Constants.sol b/src/aggregate-router/libraries/Constants.sol index c264376..346012f 100644 --- a/src/aggregate-router/libraries/Constants.sol +++ b/src/aggregate-router/libraries/Constants.sol @@ -1,40 +1,40 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {IWETH9} from '../interfaces/external/IWETH9.sol'; +import { IWETH9 } from "../interfaces/external/IWETH9.sol"; /// @title Constant state /// @notice Constant state used by the Universal Router library Constants { - /// @dev Used for identifying cases when this contract's balance of a token is to be used as an input - /// This value is equivalent to 1<<255, i.e. a singular 1 in the most significant bit. - uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000; + /// @dev Used for identifying cases when this contract's balance of a token is to be used as an input + /// This value is equivalent to 1<<255, i.e. a singular 1 in the most significant bit. + uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000; - /// @dev Used for identifying cases when a v2 pair has already received input tokens - uint256 internal constant ALREADY_PAID = 0; + /// @dev Used for identifying cases when a v2 pair has already received input tokens + uint256 internal constant ALREADY_PAID = 0; - /// @dev Used as a flag for identifying the transfer of ETH instead of a token - address internal constant ETH = address(0); + /// @dev Used as a flag for identifying the transfer of ETH instead of a token + address internal constant ETH = address(0); - /// @dev Used as a flag for identifying that msg.sender should be used, saves gas by sending more 0 bytes - address internal constant MSG_SENDER = address(1); + /// @dev Used as a flag for identifying that msg.sender should be used, saves gas by sending more 0 bytes + address internal constant MSG_SENDER = address(1); - /// @dev Used as a flag for identifying address(this) should be used, saves gas by sending more 0 bytes - address internal constant ADDRESS_THIS = address(2); + /// @dev Used as a flag for identifying address(this) should be used, saves gas by sending more 0 bytes + address internal constant ADDRESS_THIS = address(2); - /// @dev The length of the bytes encoded address - uint256 internal constant ADDR_SIZE = 20; + /// @dev The length of the bytes encoded address + uint256 internal constant ADDR_SIZE = 20; - /// @dev The length of the bytes encoded fee - uint256 internal constant V3_FEE_SIZE = 3; + /// @dev The length of the bytes encoded fee + uint256 internal constant V3_FEE_SIZE = 3; - /// @dev The offset of a single token address (20) and pool fee (3) - uint256 internal constant NEXT_V3_POOL_OFFSET = ADDR_SIZE + V3_FEE_SIZE; + /// @dev The offset of a single token address (20) and pool fee (3) + uint256 internal constant NEXT_V3_POOL_OFFSET = ADDR_SIZE + V3_FEE_SIZE; - /// @dev The offset of an encoded pool key - /// Token (20) + Fee (3) + Token (20) = 43 - uint256 internal constant V3_POP_OFFSET = NEXT_V3_POOL_OFFSET + ADDR_SIZE; + /// @dev The offset of an encoded pool key + /// Token (20) + Fee (3) + Token (20) = 43 + uint256 internal constant V3_POP_OFFSET = NEXT_V3_POOL_OFFSET + ADDR_SIZE; - /// @dev The minimum length of an encoding that contains 2 or more pools - uint256 internal constant MULTIPLE_V3_POOLS_MIN_LENGTH = V3_POP_OFFSET + NEXT_V3_POOL_OFFSET; + /// @dev The minimum length of an encoding that contains 2 or more pools + uint256 internal constant MULTIPLE_V3_POOLS_MIN_LENGTH = V3_POP_OFFSET + NEXT_V3_POOL_OFFSET; } diff --git a/src/aggregate-router/modules/NFTImmutables.sol b/src/aggregate-router/modules/NFTImmutables.sol index c44613e..2d89b69 100644 --- a/src/aggregate-router/modules/NFTImmutables.sol +++ b/src/aggregate-router/modules/NFTImmutables.sol @@ -1,73 +1,73 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {ERC20} from 'solmate/src/tokens/ERC20.sol'; +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; struct NFTParameters { - address seaportV1_5; - address seaportV1_4; - address nftxZap; - address x2y2; - address foundation; - address sudoswap; - address elementMarket; - address nft20Zap; - address cryptopunks; - address looksRareV2; - address routerRewardsDistributor; - address looksRareRewardsDistributor; - address looksRareToken; + address seaportV1_5; + address seaportV1_4; + address nftxZap; + address x2y2; + address foundation; + address sudoswap; + address elementMarket; + address nft20Zap; + address cryptopunks; + address looksRareV2; + address routerRewardsDistributor; + address looksRareRewardsDistributor; + address looksRareToken; } contract NFTImmutables { - /// @dev Seaport 1.5 address - address internal immutable SEAPORT_V1_5; + /// @dev Seaport 1.5 address + address internal immutable SEAPORT_V1_5; - /// @dev Seaport 1.4 address - address internal immutable SEAPORT_V1_4; + /// @dev Seaport 1.4 address + address internal immutable SEAPORT_V1_4; - /// @dev The address of NFTX zap contract for interfacing with vaults - address internal immutable NFTX_ZAP; + /// @dev The address of NFTX zap contract for interfacing with vaults + address internal immutable NFTX_ZAP; - /// @dev The address of X2Y2 - address internal immutable X2Y2; + /// @dev The address of X2Y2 + address internal immutable X2Y2; - // @dev The address of Foundation - address internal immutable FOUNDATION; + // @dev The address of Foundation + address internal immutable FOUNDATION; - // @dev The address of Element Market - address internal immutable ELEMENT_MARKET; + // @dev The address of Element Market + address internal immutable ELEMENT_MARKET; - // @dev the address of NFT20's zap contract - address internal immutable NFT20_ZAP; + // @dev the address of NFT20's zap contract + address internal immutable NFT20_ZAP; - // @dev the address of Larva Lab's cryptopunks marketplace - address internal immutable CRYPTOPUNKS; + // @dev the address of Larva Lab's cryptopunks marketplace + address internal immutable CRYPTOPUNKS; - /// @dev The address of LooksRareV2 - address internal immutable LOOKS_RARE_V2; + /// @dev The address of LooksRareV2 + address internal immutable LOOKS_RARE_V2; - /// @dev The address of LooksRare token - ERC20 internal immutable LOOKS_RARE_TOKEN; + /// @dev The address of LooksRare token + ERC20 internal immutable LOOKS_RARE_TOKEN; - /// @dev The address of LooksRare rewards distributor - address internal immutable LOOKS_RARE_REWARDS_DISTRIBUTOR; + /// @dev The address of LooksRare rewards distributor + address internal immutable LOOKS_RARE_REWARDS_DISTRIBUTOR; - /// @dev The address of router rewards distributor - address internal immutable ROUTER_REWARDS_DISTRIBUTOR; + /// @dev The address of router rewards distributor + address internal immutable ROUTER_REWARDS_DISTRIBUTOR; - constructor(NFTParameters memory params) { - SEAPORT_V1_5 = params.seaportV1_5; - SEAPORT_V1_4 = params.seaportV1_4; - NFTX_ZAP = params.nftxZap; - X2Y2 = params.x2y2; - FOUNDATION = params.foundation; - ELEMENT_MARKET = params.elementMarket; - NFT20_ZAP = params.nft20Zap; - CRYPTOPUNKS = params.cryptopunks; - LOOKS_RARE_V2 = params.looksRareV2; - LOOKS_RARE_TOKEN = ERC20(params.looksRareToken); - LOOKS_RARE_REWARDS_DISTRIBUTOR = params.looksRareRewardsDistributor; - ROUTER_REWARDS_DISTRIBUTOR = params.routerRewardsDistributor; - } + constructor(NFTParameters memory params) { + SEAPORT_V1_5 = params.seaportV1_5; + SEAPORT_V1_4 = params.seaportV1_4; + NFTX_ZAP = params.nftxZap; + X2Y2 = params.x2y2; + FOUNDATION = params.foundation; + ELEMENT_MARKET = params.elementMarket; + NFT20_ZAP = params.nft20Zap; + CRYPTOPUNKS = params.cryptopunks; + LOOKS_RARE_V2 = params.looksRareV2; + LOOKS_RARE_TOKEN = ERC20(params.looksRareToken); + LOOKS_RARE_REWARDS_DISTRIBUTOR = params.looksRareRewardsDistributor; + ROUTER_REWARDS_DISTRIBUTOR = params.routerRewardsDistributor; + } } diff --git a/src/aggregate-router/modules/Payments.sol b/src/aggregate-router/modules/Payments.sol index a752582..c67a037 100644 --- a/src/aggregate-router/modules/Payments.sol +++ b/src/aggregate-router/modules/Payments.sol @@ -1,140 +1,140 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {Constants} from '../libraries/Constants.sol'; -import {PaymentsImmutables} from '../modules/PaymentsImmutables.sol'; -import {SafeTransferLib} from 'solmate/src/utils/SafeTransferLib.sol'; -import {ERC20} from 'solmate/src/tokens/ERC20.sol'; -import {ERC721} from 'solmate/src/tokens/ERC721.sol'; -import {ERC1155} from 'solmate/src/tokens/ERC1155.sol'; +import { Constants } from "../libraries/Constants.sol"; +import { PaymentsImmutables } from "../modules/PaymentsImmutables.sol"; +import { SafeTransferLib } from "solmate/src/utils/SafeTransferLib.sol"; +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; +import { ERC721 } from "solmate/src/tokens/ERC721.sol"; +import { ERC1155 } from "solmate/src/tokens/ERC1155.sol"; /// @title Payments contract /// @notice Performs various operations around the payment of ETH and tokens abstract contract Payments is PaymentsImmutables { - using SafeTransferLib for ERC20; - using SafeTransferLib for address; + using SafeTransferLib for ERC20; + using SafeTransferLib for address; - error InsufficientToken(); - error InsufficientETH(); - error InvalidBips(); - error InvalidSpender(); + error InsufficientToken(); + error InsufficientETH(); + error InvalidBips(); + error InvalidSpender(); - uint256 internal constant FEE_BIPS_BASE = 10_000; + uint256 internal constant FEE_BIPS_BASE = 10_000; - /// @notice Pays an amount of ETH or ERC20 to a recipient - /// @param token The token to pay (can be ETH using Constants.ETH) - /// @param recipient The address that will receive the payment - /// @param value The amount to pay - function pay(address token, address recipient, uint256 value) internal { - if (token == Constants.ETH) { - recipient.safeTransferETH(value); - } else { - if (value == Constants.CONTRACT_BALANCE) { - value = ERC20(token).balanceOf(address(this)); - } + /// @notice Pays an amount of ETH or ERC20 to a recipient + /// @param token The token to pay (can be ETH using Constants.ETH) + /// @param recipient The address that will receive the payment + /// @param value The amount to pay + function pay(address token, address recipient, uint256 value) internal { + if (token == Constants.ETH) { + recipient.safeTransferETH(value); + } else { + if (value == Constants.CONTRACT_BALANCE) { + value = ERC20(token).balanceOf(address(this)); + } - ERC20(token).safeTransfer(recipient, value); - } + ERC20(token).safeTransfer(recipient, value); } + } - /// @notice Approves a protocol to spend ERC20s in the router - /// @param token The token to approve - /// @param spender Which protocol to approve - function approveERC20(ERC20 token, Spenders spender) internal { - // check spender is one of our approved spenders - address spenderAddress; - /// @dev use 0 = Opensea Conduit for both Seaport v1.4 and v1.5 - if (spender == Spenders.OSConduit) spenderAddress = OPENSEA_CONDUIT; - else if (spender == Spenders.Sudoswap) spenderAddress = SUDOSWAP; - else revert InvalidSpender(); + /// @notice Approves a protocol to spend ERC20s in the router + /// @param token The token to approve + /// @param spender Which protocol to approve + function approveERC20(ERC20 token, Spenders spender) internal { + // check spender is one of our approved spenders + address spenderAddress; + /// @dev use 0 = Opensea Conduit for both Seaport v1.4 and v1.5 + if (spender == Spenders.OSConduit) spenderAddress = OPENSEA_CONDUIT; + else if (spender == Spenders.Sudoswap) spenderAddress = SUDOSWAP; + else revert InvalidSpender(); - // set approval - token.safeApprove(spenderAddress, type(uint256).max); - } + // set approval + token.safeApprove(spenderAddress, type(uint256).max); + } - /// @notice Pays a proportion of the contract's ETH or ERC20 to a recipient - /// @param token The token to pay (can be ETH using Constants.ETH) - /// @param recipient The address that will receive payment - /// @param bips Portion in bips of whole balance of the contract - function payPortion(address token, address recipient, uint256 bips) internal { - if (bips == 0 || bips > FEE_BIPS_BASE) revert InvalidBips(); - if (token == Constants.ETH) { - uint256 balance = address(this).balance; - uint256 amount = (balance * bips) / FEE_BIPS_BASE; - recipient.safeTransferETH(amount); - } else { - uint256 balance = ERC20(token).balanceOf(address(this)); - uint256 amount = (balance * bips) / FEE_BIPS_BASE; - ERC20(token).safeTransfer(recipient, amount); - } + /// @notice Pays a proportion of the contract's ETH or ERC20 to a recipient + /// @param token The token to pay (can be ETH using Constants.ETH) + /// @param recipient The address that will receive payment + /// @param bips Portion in bips of whole balance of the contract + function payPortion(address token, address recipient, uint256 bips) internal { + if (bips == 0 || bips > FEE_BIPS_BASE) revert InvalidBips(); + if (token == Constants.ETH) { + uint256 balance = address(this).balance; + uint256 amount = (balance * bips) / FEE_BIPS_BASE; + recipient.safeTransferETH(amount); + } else { + uint256 balance = ERC20(token).balanceOf(address(this)); + uint256 amount = (balance * bips) / FEE_BIPS_BASE; + ERC20(token).safeTransfer(recipient, amount); } + } - /// @notice Sweeps all of the contract's ERC20 or ETH to an address - /// @param token The token to sweep (can be ETH using Constants.ETH) - /// @param recipient The address that will receive payment - /// @param amountMinimum The minimum desired amount - function sweep(address token, address recipient, uint256 amountMinimum) internal { - uint256 balance; - if (token == Constants.ETH) { - balance = address(this).balance; - if (balance < amountMinimum) revert InsufficientETH(); - if (balance > 0) recipient.safeTransferETH(balance); - } else { - balance = ERC20(token).balanceOf(address(this)); - if (balance < amountMinimum) revert InsufficientToken(); - if (balance > 0) ERC20(token).safeTransfer(recipient, balance); - } + /// @notice Sweeps all of the contract's ERC20 or ETH to an address + /// @param token The token to sweep (can be ETH using Constants.ETH) + /// @param recipient The address that will receive payment + /// @param amountMinimum The minimum desired amount + function sweep(address token, address recipient, uint256 amountMinimum) internal { + uint256 balance; + if (token == Constants.ETH) { + balance = address(this).balance; + if (balance < amountMinimum) revert InsufficientETH(); + if (balance > 0) recipient.safeTransferETH(balance); + } else { + balance = ERC20(token).balanceOf(address(this)); + if (balance < amountMinimum) revert InsufficientToken(); + if (balance > 0) ERC20(token).safeTransfer(recipient, balance); } + } - /// @notice Sweeps an ERC721 to a recipient from the contract - /// @param token The ERC721 token to sweep - /// @param recipient The address that will receive payment - /// @param id The ID of the ERC721 to sweep - function sweepERC721(address token, address recipient, uint256 id) internal { - ERC721(token).safeTransferFrom(address(this), recipient, id); - } + /// @notice Sweeps an ERC721 to a recipient from the contract + /// @param token The ERC721 token to sweep + /// @param recipient The address that will receive payment + /// @param id The ID of the ERC721 to sweep + function sweepERC721(address token, address recipient, uint256 id) internal { + ERC721(token).safeTransferFrom(address(this), recipient, id); + } - /// @notice Sweeps all of the contract's ERC1155 to an address - /// @param token The ERC1155 token to sweep - /// @param recipient The address that will receive payment - /// @param id The ID of the ERC1155 to sweep - /// @param amountMinimum The minimum desired amount - function sweepERC1155(address token, address recipient, uint256 id, uint256 amountMinimum) internal { - uint256 balance = ERC1155(token).balanceOf(address(this), id); - if (balance < amountMinimum) revert InsufficientToken(); - ERC1155(token).safeTransferFrom(address(this), recipient, id, balance, bytes('')); - } + /// @notice Sweeps all of the contract's ERC1155 to an address + /// @param token The ERC1155 token to sweep + /// @param recipient The address that will receive payment + /// @param id The ID of the ERC1155 to sweep + /// @param amountMinimum The minimum desired amount + function sweepERC1155(address token, address recipient, uint256 id, uint256 amountMinimum) internal { + uint256 balance = ERC1155(token).balanceOf(address(this), id); + if (balance < amountMinimum) revert InsufficientToken(); + ERC1155(token).safeTransferFrom(address(this), recipient, id, balance, bytes("")); + } - /// @notice Wraps an amount of ETH into WETH - /// @param recipient The recipient of the WETH - /// @param amount The amount to wrap (can be CONTRACT_BALANCE) - function wrapETH(address recipient, uint256 amount) internal { - if (amount == Constants.CONTRACT_BALANCE) { - amount = address(this).balance; - } else if (amount > address(this).balance) { - revert InsufficientETH(); - } - if (amount > 0) { - WETH9.deposit{value: amount}(); - if (recipient != address(this)) { - WETH9.transfer(recipient, amount); - } - } + /// @notice Wraps an amount of ETH into WETH + /// @param recipient The recipient of the WETH + /// @param amount The amount to wrap (can be CONTRACT_BALANCE) + function wrapETH(address recipient, uint256 amount) internal { + if (amount == Constants.CONTRACT_BALANCE) { + amount = address(this).balance; + } else if (amount > address(this).balance) { + revert InsufficientETH(); } + if (amount > 0) { + WETH9.deposit{ value: amount }(); + if (recipient != address(this)) { + WETH9.transfer(recipient, amount); + } + } + } - /// @notice Unwraps all of the contract's WETH into ETH - /// @param recipient The recipient of the ETH - /// @param amountMinimum The minimum amount of ETH desired - function unwrapWETH9(address recipient, uint256 amountMinimum) internal { - uint256 value = WETH9.balanceOf(address(this)); - if (value < amountMinimum) { - revert InsufficientETH(); - } - if (value > 0) { - WETH9.withdraw(value); - if (recipient != address(this)) { - recipient.safeTransferETH(value); - } - } + /// @notice Unwraps all of the contract's WETH into ETH + /// @param recipient The recipient of the ETH + /// @param amountMinimum The minimum amount of ETH desired + function unwrapWETH9(address recipient, uint256 amountMinimum) internal { + uint256 value = WETH9.balanceOf(address(this)); + if (value < amountMinimum) { + revert InsufficientETH(); + } + if (value > 0) { + WETH9.withdraw(value); + if (recipient != address(this)) { + recipient.safeTransferETH(value); + } } + } } diff --git a/src/aggregate-router/modules/PaymentsImmutables.sol b/src/aggregate-router/modules/PaymentsImmutables.sol index 713cb59..fa5224f 100644 --- a/src/aggregate-router/modules/PaymentsImmutables.sol +++ b/src/aggregate-router/modules/PaymentsImmutables.sol @@ -1,38 +1,38 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {IWETH9} from '../interfaces/external/IWETH9.sol'; -import {IAllowanceTransfer} from 'permit2/src/interfaces/IAllowanceTransfer.sol'; +import { IWETH9 } from "../interfaces/external/IWETH9.sol"; +import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol"; struct PaymentsParameters { - address permit2; - address weth9; - address openseaConduit; - address sudoswap; + address permit2; + address weth9; + address openseaConduit; + address sudoswap; } contract PaymentsImmutables { - /// @dev WETH9 address - IWETH9 internal immutable WETH9; + /// @dev WETH9 address + IWETH9 internal immutable WETH9; - /// @dev Permit2 address - IAllowanceTransfer internal immutable PERMIT2; + /// @dev Permit2 address + IAllowanceTransfer internal immutable PERMIT2; - /// @dev The address of OpenSea's conduit used in both Seaport 1.4 and Seaport 1.5 - address internal immutable OPENSEA_CONDUIT; + /// @dev The address of OpenSea's conduit used in both Seaport 1.4 and Seaport 1.5 + address internal immutable OPENSEA_CONDUIT; - // @dev The address of Sudoswap's router - address internal immutable SUDOSWAP; + // @dev The address of Sudoswap's router + address internal immutable SUDOSWAP; - enum Spenders { - OSConduit, - Sudoswap - } + enum Spenders { + OSConduit, + Sudoswap + } - constructor(PaymentsParameters memory params) { - WETH9 = IWETH9(params.weth9); - PERMIT2 = IAllowanceTransfer(params.permit2); - OPENSEA_CONDUIT = params.openseaConduit; - SUDOSWAP = params.sudoswap; - } + constructor(PaymentsParameters memory params) { + WETH9 = IWETH9(params.weth9); + PERMIT2 = IAllowanceTransfer(params.permit2); + OPENSEA_CONDUIT = params.openseaConduit; + SUDOSWAP = params.sudoswap; + } } diff --git a/src/aggregate-router/modules/Permit2Payments.sol b/src/aggregate-router/modules/Permit2Payments.sol index bcfca98..2ee9687 100644 --- a/src/aggregate-router/modules/Permit2Payments.sol +++ b/src/aggregate-router/modules/Permit2Payments.sol @@ -1,45 +1,45 @@ pragma solidity ^0.8.17; -import {IAllowanceTransfer} from 'permit2/src/interfaces/IAllowanceTransfer.sol'; -import {SafeCast160} from 'permit2/src/libraries/SafeCast160.sol'; -import {Payments} from './Payments.sol'; -import {Constants} from '../libraries/Constants.sol'; +import { IAllowanceTransfer } from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import { SafeCast160 } from "permit2/src/libraries/SafeCast160.sol"; +import { Payments } from "./Payments.sol"; +import { Constants } from "../libraries/Constants.sol"; /// @title Payments through Permit2 /// @notice Performs interactions with Permit2 to transfer tokens abstract contract Permit2Payments is Payments { - using SafeCast160 for uint256; + using SafeCast160 for uint256; - error FromAddressIsNotOwner(); + error FromAddressIsNotOwner(); - /// @notice Performs a transferFrom on Permit2 - /// @param token The token to transfer - /// @param from The address to transfer from - /// @param to The recipient of the transfer - /// @param amount The amount to transfer - function permit2TransferFrom(address token, address from, address to, uint160 amount) internal { - PERMIT2.transferFrom(from, to, amount, token); - } + /// @notice Performs a transferFrom on Permit2 + /// @param token The token to transfer + /// @param from The address to transfer from + /// @param to The recipient of the transfer + /// @param amount The amount to transfer + function permit2TransferFrom(address token, address from, address to, uint160 amount) internal { + PERMIT2.transferFrom(from, to, amount, token); + } - /// @notice Performs a batch transferFrom on Permit2 - /// @param batchDetails An array detailing each of the transfers that should occur - function permit2TransferFrom(IAllowanceTransfer.AllowanceTransferDetails[] memory batchDetails, address owner) - internal - { - uint256 batchLength = batchDetails.length; - for (uint256 i = 0; i < batchLength; ++i) { - if (batchDetails[i].from != owner) revert FromAddressIsNotOwner(); - } - PERMIT2.transferFrom(batchDetails); + /// @notice Performs a batch transferFrom on Permit2 + /// @param batchDetails An array detailing each of the transfers that should occur + function permit2TransferFrom(IAllowanceTransfer.AllowanceTransferDetails[] memory batchDetails, address owner) + internal + { + uint256 batchLength = batchDetails.length; + for (uint256 i = 0; i < batchLength; ++i) { + if (batchDetails[i].from != owner) revert FromAddressIsNotOwner(); } + PERMIT2.transferFrom(batchDetails); + } - /// @notice Either performs a regular payment or transferFrom on Permit2, depending on the payer address - /// @param token The token to transfer - /// @param payer The address to pay for the transfer - /// @param recipient The recipient of the transfer - /// @param amount The amount to transfer - function payOrPermit2Transfer(address token, address payer, address recipient, uint256 amount) internal { - if (payer == address(this)) pay(token, recipient, amount); - else permit2TransferFrom(token, payer, recipient, amount.toUint160()); - } + /// @notice Either performs a regular payment or transferFrom on Permit2, depending on the payer address + /// @param token The token to transfer + /// @param payer The address to pay for the transfer + /// @param recipient The recipient of the transfer + /// @param amount The amount to transfer + function payOrPermit2Transfer(address token, address payer, address recipient, uint256 amount) internal { + if (payer == address(this)) pay(token, recipient, amount); + else permit2TransferFrom(token, payer, recipient, amount.toUint160()); + } } diff --git a/src/aggregate-router/modules/uniswap/UniswapImmutables.sol b/src/aggregate-router/modules/uniswap/UniswapImmutables.sol index 1cdcd03..c1e7a30 100644 --- a/src/aggregate-router/modules/uniswap/UniswapImmutables.sol +++ b/src/aggregate-router/modules/uniswap/UniswapImmutables.sol @@ -2,29 +2,29 @@ pragma solidity ^0.8.17; struct UniswapParameters { - address v2Factory; - address v3Factory; - bytes32 pairInitCodeHash; - bytes32 poolInitCodeHash; + address v2Factory; + address v3Factory; + bytes32 pairInitCodeHash; + bytes32 poolInitCodeHash; } contract UniswapImmutables { - /// @dev The address of UniswapV2Factory - address internal immutable UNISWAP_V2_FACTORY; + /// @dev The address of UniswapV2Factory + address internal immutable UNISWAP_V2_FACTORY; - /// @dev The UniswapV2Pair initcodehash - bytes32 internal immutable UNISWAP_V2_PAIR_INIT_CODE_HASH; + /// @dev The UniswapV2Pair initcodehash + bytes32 internal immutable UNISWAP_V2_PAIR_INIT_CODE_HASH; - /// @dev The address of UniswapV3Factory - address internal immutable UNISWAP_V3_FACTORY; + /// @dev The address of UniswapV3Factory + address internal immutable UNISWAP_V3_FACTORY; - /// @dev The UniswapV3Pool initcodehash - bytes32 internal immutable UNISWAP_V3_POOL_INIT_CODE_HASH; + /// @dev The UniswapV3Pool initcodehash + bytes32 internal immutable UNISWAP_V3_POOL_INIT_CODE_HASH; - constructor(UniswapParameters memory params) { - UNISWAP_V2_FACTORY = params.v2Factory; - UNISWAP_V2_PAIR_INIT_CODE_HASH = params.pairInitCodeHash; - UNISWAP_V3_FACTORY = params.v3Factory; - UNISWAP_V3_POOL_INIT_CODE_HASH = params.poolInitCodeHash; - } + constructor(UniswapParameters memory params) { + UNISWAP_V2_FACTORY = params.v2Factory; + UNISWAP_V2_PAIR_INIT_CODE_HASH = params.pairInitCodeHash; + UNISWAP_V3_FACTORY = params.v3Factory; + UNISWAP_V3_POOL_INIT_CODE_HASH = params.poolInitCodeHash; + } } diff --git a/src/aggregate-router/modules/uniswap/v2/UniswapV2Library.sol b/src/aggregate-router/modules/uniswap/v2/UniswapV2Library.sol index 936d7cd..5e04452 100644 --- a/src/aggregate-router/modules/uniswap/v2/UniswapV2Library.sol +++ b/src/aggregate-router/modules/uniswap/v2/UniswapV2Library.sol @@ -1,149 +1,147 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.0; -import {IUniswapV2Pair} from '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol'; +import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; /// @title Uniswap v2 Helper Library /// @notice Calculates the recipient address for a command library UniswapV2Library { - error InvalidReserves(); - error InvalidPath(); + error InvalidReserves(); + error InvalidPath(); - /// @notice Calculates the v2 address for a pair without making any external calls - /// @param factory The address of the v2 factory - /// @param initCodeHash The hash of the pair initcode - /// @param tokenA One of the tokens in the pair - /// @param tokenB The other token in the pair - /// @return pair The resultant v2 pair address - function pairFor(address factory, bytes32 initCodeHash, address tokenA, address tokenB) - internal - pure - returns (address pair) - { - (address token0, address token1) = sortTokens(tokenA, tokenB); - pair = pairForPreSorted(factory, initCodeHash, token0, token1); - } + /// @notice Calculates the v2 address for a pair without making any external calls + /// @param factory The address of the v2 factory + /// @param initCodeHash The hash of the pair initcode + /// @param tokenA One of the tokens in the pair + /// @param tokenB The other token in the pair + /// @return pair The resultant v2 pair address + function pairFor(address factory, bytes32 initCodeHash, address tokenA, address tokenB) + internal + pure + returns (address pair) + { + (address token0, address token1) = sortTokens(tokenA, tokenB); + pair = pairForPreSorted(factory, initCodeHash, token0, token1); + } - /// @notice Calculates the v2 address for a pair and the pair's token0 - /// @param factory The address of the v2 factory - /// @param initCodeHash The hash of the pair initcode - /// @param tokenA One of the tokens in the pair - /// @param tokenB The other token in the pair - /// @return pair The resultant v2 pair address - /// @return token0 The token considered token0 in this pair - function pairAndToken0For(address factory, bytes32 initCodeHash, address tokenA, address tokenB) - internal - pure - returns (address pair, address token0) - { - address token1; - (token0, token1) = sortTokens(tokenA, tokenB); - pair = pairForPreSorted(factory, initCodeHash, token0, token1); - } + /// @notice Calculates the v2 address for a pair and the pair's token0 + /// @param factory The address of the v2 factory + /// @param initCodeHash The hash of the pair initcode + /// @param tokenA One of the tokens in the pair + /// @param tokenB The other token in the pair + /// @return pair The resultant v2 pair address + /// @return token0 The token considered token0 in this pair + function pairAndToken0For(address factory, bytes32 initCodeHash, address tokenA, address tokenB) + internal + pure + returns (address pair, address token0) + { + address token1; + (token0, token1) = sortTokens(tokenA, tokenB); + pair = pairForPreSorted(factory, initCodeHash, token0, token1); + } - /// @notice Calculates the v2 address for a pair assuming the input tokens are pre-sorted - /// @param factory The address of the v2 factory - /// @param initCodeHash The hash of the pair initcode - /// @param token0 The pair's token0 - /// @param token1 The pair's token1 - /// @return pair The resultant v2 pair address - function pairForPreSorted(address factory, bytes32 initCodeHash, address token0, address token1) - private - pure - returns (address pair) - { - pair = address( - uint160( - uint256( - keccak256( - abi.encodePacked(hex'ff', factory, keccak256(abi.encodePacked(token0, token1)), initCodeHash) - ) - ) - ) - ); - } + /// @notice Calculates the v2 address for a pair assuming the input tokens are pre-sorted + /// @param factory The address of the v2 factory + /// @param initCodeHash The hash of the pair initcode + /// @param token0 The pair's token0 + /// @param token1 The pair's token1 + /// @return pair The resultant v2 pair address + function pairForPreSorted(address factory, bytes32 initCodeHash, address token0, address token1) + private + pure + returns (address pair) + { + pair = address( + uint160( + uint256( + keccak256(abi.encodePacked(hex"ff", factory, keccak256(abi.encodePacked(token0, token1)), initCodeHash)) + ) + ) + ); + } - /// @notice Calculates the v2 address for a pair and fetches the reserves for each token - /// @param factory The address of the v2 factory - /// @param initCodeHash The hash of the pair initcode - /// @param tokenA One of the tokens in the pair - /// @param tokenB The other token in the pair - /// @return pair The resultant v2 pair address - /// @return reserveA The reserves for tokenA - /// @return reserveB The reserves for tokenB - function pairAndReservesFor(address factory, bytes32 initCodeHash, address tokenA, address tokenB) - private - view - returns (address pair, uint256 reserveA, uint256 reserveB) - { - address token0; - (pair, token0) = pairAndToken0For(factory, initCodeHash, tokenA, tokenB); - (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); - (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); - } + /// @notice Calculates the v2 address for a pair and fetches the reserves for each token + /// @param factory The address of the v2 factory + /// @param initCodeHash The hash of the pair initcode + /// @param tokenA One of the tokens in the pair + /// @param tokenB The other token in the pair + /// @return pair The resultant v2 pair address + /// @return reserveA The reserves for tokenA + /// @return reserveB The reserves for tokenB + function pairAndReservesFor(address factory, bytes32 initCodeHash, address tokenA, address tokenB) + private + view + returns (address pair, uint256 reserveA, uint256 reserveB) + { + address token0; + (pair, token0) = pairAndToken0For(factory, initCodeHash, tokenA, tokenB); + (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); + (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + } - /// @notice Given an input asset amount returns the maximum output amount of the other asset - /// @param amountIn The token input amount - /// @param reserveIn The reserves available of the input token - /// @param reserveOut The reserves available of the output token - /// @return amountOut The output amount of the output token - function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) - internal - pure - returns (uint256 amountOut) - { - if (reserveIn == 0 || reserveOut == 0) revert InvalidReserves(); - uint256 amountInWithFee = amountIn * 997; - uint256 numerator = amountInWithFee * reserveOut; - uint256 denominator = reserveIn * 1000 + amountInWithFee; - amountOut = numerator / denominator; - } + /// @notice Given an input asset amount returns the maximum output amount of the other asset + /// @param amountIn The token input amount + /// @param reserveIn The reserves available of the input token + /// @param reserveOut The reserves available of the output token + /// @return amountOut The output amount of the output token + function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) + internal + pure + returns (uint256 amountOut) + { + if (reserveIn == 0 || reserveOut == 0) revert InvalidReserves(); + uint256 amountInWithFee = amountIn * 997; + uint256 numerator = amountInWithFee * reserveOut; + uint256 denominator = reserveIn * 1000 + amountInWithFee; + amountOut = numerator / denominator; + } - /// @notice Returns the input amount needed for a desired output amount in a single-hop trade - /// @param amountOut The desired output amount - /// @param reserveIn The reserves available of the input token - /// @param reserveOut The reserves available of the output token - /// @return amountIn The input amount of the input token - function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) - internal - pure - returns (uint256 amountIn) - { - if (reserveIn == 0 || reserveOut == 0) revert InvalidReserves(); - uint256 numerator = reserveIn * amountOut * 1000; - uint256 denominator = (reserveOut - amountOut) * 997; - amountIn = (numerator / denominator) + 1; - } + /// @notice Returns the input amount needed for a desired output amount in a single-hop trade + /// @param amountOut The desired output amount + /// @param reserveIn The reserves available of the input token + /// @param reserveOut The reserves available of the output token + /// @return amountIn The input amount of the input token + function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) + internal + pure + returns (uint256 amountIn) + { + if (reserveIn == 0 || reserveOut == 0) revert InvalidReserves(); + uint256 numerator = reserveIn * amountOut * 1000; + uint256 denominator = (reserveOut - amountOut) * 997; + amountIn = (numerator / denominator) + 1; + } - /// @notice Returns the input amount needed for a desired output amount in a multi-hop trade - /// @param factory The address of the v2 factory - /// @param initCodeHash The hash of the pair initcode - /// @param amountOut The desired output amount - /// @param path The path of the multi-hop trade - /// @return amount The input amount of the input token - /// @return pair The first pair in the trade - function getAmountInMultihop(address factory, bytes32 initCodeHash, uint256 amountOut, address[] memory path) - internal - view - returns (uint256 amount, address pair) - { - if (path.length < 2) revert InvalidPath(); - amount = amountOut; - for (uint256 i = path.length - 1; i > 0; i--) { - uint256 reserveIn; - uint256 reserveOut; + /// @notice Returns the input amount needed for a desired output amount in a multi-hop trade + /// @param factory The address of the v2 factory + /// @param initCodeHash The hash of the pair initcode + /// @param amountOut The desired output amount + /// @param path The path of the multi-hop trade + /// @return amount The input amount of the input token + /// @return pair The first pair in the trade + function getAmountInMultihop(address factory, bytes32 initCodeHash, uint256 amountOut, address[] memory path) + internal + view + returns (uint256 amount, address pair) + { + if (path.length < 2) revert InvalidPath(); + amount = amountOut; + for (uint256 i = path.length - 1; i > 0; i--) { + uint256 reserveIn; + uint256 reserveOut; - (pair, reserveIn, reserveOut) = pairAndReservesFor(factory, initCodeHash, path[i - 1], path[i]); - amount = getAmountIn(amount, reserveIn, reserveOut); - } + (pair, reserveIn, reserveOut) = pairAndReservesFor(factory, initCodeHash, path[i - 1], path[i]); + amount = getAmountIn(amount, reserveIn, reserveOut); } + } - /// @notice Sorts two tokens to return token0 and token1 - /// @param tokenA The first token to sort - /// @param tokenB The other token to sort - /// @return token0 The smaller token by address value - /// @return token1 The larger token by address value - function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { - (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); - } + /// @notice Sorts two tokens to return token0 and token1 + /// @param tokenA The first token to sort + /// @param tokenB The other token to sort + /// @return token0 The smaller token by address value + /// @return token1 The larger token by address value + function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) { + (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA); + } } diff --git a/src/aggregate-router/modules/uniswap/v2/V2SwapRouter.sol b/src/aggregate-router/modules/uniswap/v2/V2SwapRouter.sol index ee2aff4..bb0bcdd 100644 --- a/src/aggregate-router/modules/uniswap/v2/V2SwapRouter.sol +++ b/src/aggregate-router/modules/uniswap/v2/V2SwapRouter.sol @@ -1,97 +1,93 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {IUniswapV2Pair} from '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol'; -import {UniswapV2Library} from './UniswapV2Library.sol'; -import {UniswapImmutables} from '../UniswapImmutables.sol'; -import {Payments} from '../../Payments.sol'; -import {Permit2Payments} from '../../Permit2Payments.sol'; -import {Constants} from '../../../libraries/Constants.sol'; -import {ERC20} from 'solmate/src/tokens/ERC20.sol'; +import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; +import { UniswapV2Library } from "./UniswapV2Library.sol"; +import { UniswapImmutables } from "../UniswapImmutables.sol"; +import { Payments } from "../../Payments.sol"; +import { Permit2Payments } from "../../Permit2Payments.sol"; +import { Constants } from "../../../libraries/Constants.sol"; +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; /// @title Router for Uniswap v2 Trades abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments { - error V2TooLittleReceived(); - error V2TooMuchRequested(); - error V2InvalidPath(); + error V2TooLittleReceived(); + error V2TooMuchRequested(); + error V2InvalidPath(); - function _v2Swap(address[] calldata path, address recipient, address pair) private { - unchecked { - if (path.length < 2) revert V2InvalidPath(); + function _v2Swap(address[] calldata path, address recipient, address pair) private { + unchecked { + if (path.length < 2) revert V2InvalidPath(); - // cached to save on duplicate operations - (address token0,) = UniswapV2Library.sortTokens(path[0], path[1]); - uint256 finalPairIndex = path.length - 1; - uint256 penultimatePairIndex = finalPairIndex - 1; - for (uint256 i; i < finalPairIndex; i++) { - (address input, address output) = (path[i], path[i + 1]); - (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); - (uint256 reserveInput, uint256 reserveOutput) = - input == token0 ? (reserve0, reserve1) : (reserve1, reserve0); - uint256 amountInput = ERC20(input).balanceOf(pair) - reserveInput; - uint256 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput); - (uint256 amount0Out, uint256 amount1Out) = - input == token0 ? (uint256(0), amountOutput) : (amountOutput, uint256(0)); - address nextPair; - (nextPair, token0) = i < penultimatePairIndex - ? UniswapV2Library.pairAndToken0For( - UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, output, path[i + 2] - ) - : (recipient, address(0)); - IUniswapV2Pair(pair).swap(amount0Out, amount1Out, nextPair, new bytes(0)); - pair = nextPair; - } - } + // cached to save on duplicate operations + (address token0,) = UniswapV2Library.sortTokens(path[0], path[1]); + uint256 finalPairIndex = path.length - 1; + uint256 penultimatePairIndex = finalPairIndex - 1; + for (uint256 i; i < finalPairIndex; i++) { + (address input, address output) = (path[i], path[i + 1]); + (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); + (uint256 reserveInput, uint256 reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0); + uint256 amountInput = ERC20(input).balanceOf(pair) - reserveInput; + uint256 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput); + (uint256 amount0Out, uint256 amount1Out) = + input == token0 ? (uint256(0), amountOutput) : (amountOutput, uint256(0)); + address nextPair; + (nextPair, token0) = i < penultimatePairIndex + ? UniswapV2Library.pairAndToken0For(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, output, path[i + 2]) + : (recipient, address(0)); + IUniswapV2Pair(pair).swap(amount0Out, amount1Out, nextPair, new bytes(0)); + pair = nextPair; + } } + } - /// @notice Performs a Uniswap v2 exact input swap - /// @param recipient The recipient of the output tokens - /// @param amountIn The amount of input tokens for the trade - /// @param amountOutMinimum The minimum desired amount of output tokens - /// @param path The path of the trade as an array of token addresses - /// @param payer The address that will be paying the input - function v2SwapExactInput( - address recipient, - uint256 amountIn, - uint256 amountOutMinimum, - address[] calldata path, - address payer - ) internal { - address firstPair = - UniswapV2Library.pairFor(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, path[0], path[1]); - if ( - amountIn != Constants.ALREADY_PAID // amountIn of 0 to signal that the pair already has the tokens - ) { - payOrPermit2Transfer(path[0], payer, firstPair, amountIn); - } + /// @notice Performs a Uniswap v2 exact input swap + /// @param recipient The recipient of the output tokens + /// @param amountIn The amount of input tokens for the trade + /// @param amountOutMinimum The minimum desired amount of output tokens + /// @param path The path of the trade as an array of token addresses + /// @param payer The address that will be paying the input + function v2SwapExactInput( + address recipient, + uint256 amountIn, + uint256 amountOutMinimum, + address[] calldata path, + address payer + ) internal { + address firstPair = UniswapV2Library.pairFor(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, path[0], path[1]); + if ( + amountIn != Constants.ALREADY_PAID // amountIn of 0 to signal that the pair already has the tokens + ) { + payOrPermit2Transfer(path[0], payer, firstPair, amountIn); + } - ERC20 tokenOut = ERC20(path[path.length - 1]); - uint256 balanceBefore = tokenOut.balanceOf(recipient); + ERC20 tokenOut = ERC20(path[path.length - 1]); + uint256 balanceBefore = tokenOut.balanceOf(recipient); - _v2Swap(path, recipient, firstPair); + _v2Swap(path, recipient, firstPair); - uint256 amountOut = tokenOut.balanceOf(recipient) - balanceBefore; - if (amountOut < amountOutMinimum) revert V2TooLittleReceived(); - } + uint256 amountOut = tokenOut.balanceOf(recipient) - balanceBefore; + if (amountOut < amountOutMinimum) revert V2TooLittleReceived(); + } - /// @notice Performs a Uniswap v2 exact output swap - /// @param recipient The recipient of the output tokens - /// @param amountOut The amount of output tokens to receive for the trade - /// @param amountInMaximum The maximum desired amount of input tokens - /// @param path The path of the trade as an array of token addresses - /// @param payer The address that will be paying the input - function v2SwapExactOutput( - address recipient, - uint256 amountOut, - uint256 amountInMaximum, - address[] calldata path, - address payer - ) internal { - (uint256 amountIn, address firstPair) = - UniswapV2Library.getAmountInMultihop(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, amountOut, path); - if (amountIn > amountInMaximum) revert V2TooMuchRequested(); + /// @notice Performs a Uniswap v2 exact output swap + /// @param recipient The recipient of the output tokens + /// @param amountOut The amount of output tokens to receive for the trade + /// @param amountInMaximum The maximum desired amount of input tokens + /// @param path The path of the trade as an array of token addresses + /// @param payer The address that will be paying the input + function v2SwapExactOutput( + address recipient, + uint256 amountOut, + uint256 amountInMaximum, + address[] calldata path, + address payer + ) internal { + (uint256 amountIn, address firstPair) = + UniswapV2Library.getAmountInMultihop(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, amountOut, path); + if (amountIn > amountInMaximum) revert V2TooMuchRequested(); - payOrPermit2Transfer(path[0], payer, firstPair, amountIn); - _v2Swap(path, recipient, firstPair); - } + payOrPermit2Transfer(path[0], payer, firstPair, amountIn); + _v2Swap(path, recipient, firstPair); + } } diff --git a/src/aggregate-router/modules/uniswap/v3/BytesLib.sol b/src/aggregate-router/modules/uniswap/v3/BytesLib.sol index 06520ff..e2bb173 100644 --- a/src/aggregate-router/modules/uniswap/v3/BytesLib.sol +++ b/src/aggregate-router/modules/uniswap/v3/BytesLib.sol @@ -3,92 +3,88 @@ /// @title Library for Bytes Manipulation pragma solidity ^0.8.0; -import {Constants} from '../../../libraries/Constants.sol'; +import { Constants } from "../../../libraries/Constants.sol"; library BytesLib { - error SliceOutOfBounds(); + error SliceOutOfBounds(); - /// @notice Returns the address starting at byte 0 - /// @dev length and overflow checks must be carried out before calling - /// @param _bytes The input bytes string to slice - /// @return _address The address starting at byte 0 - function toAddress(bytes calldata _bytes) internal pure returns (address _address) { - if (_bytes.length < Constants.ADDR_SIZE) revert SliceOutOfBounds(); - assembly { - _address := shr(96, calldataload(_bytes.offset)) - } + /// @notice Returns the address starting at byte 0 + /// @dev length and overflow checks must be carried out before calling + /// @param _bytes The input bytes string to slice + /// @return _address The address starting at byte 0 + function toAddress(bytes calldata _bytes) internal pure returns (address _address) { + if (_bytes.length < Constants.ADDR_SIZE) revert SliceOutOfBounds(); + assembly { + _address := shr(96, calldataload(_bytes.offset)) } + } - /// @notice Returns the pool details starting at byte 0 - /// @dev length and overflow checks must be carried out before calling - /// @param _bytes The input bytes string to slice - /// @return token0 The address at byte 0 - /// @return fee The uint24 starting at byte 20 - /// @return token1 The address at byte 23 - function toPool(bytes calldata _bytes) internal pure returns (address token0, uint24 fee, address token1) { - if (_bytes.length < Constants.V3_POP_OFFSET) revert SliceOutOfBounds(); - assembly { - let firstWord := calldataload(_bytes.offset) - token0 := shr(96, firstWord) - fee := and(shr(72, firstWord), 0xffffff) - token1 := shr(96, calldataload(add(_bytes.offset, 23))) - } + /// @notice Returns the pool details starting at byte 0 + /// @dev length and overflow checks must be carried out before calling + /// @param _bytes The input bytes string to slice + /// @return token0 The address at byte 0 + /// @return fee The uint24 starting at byte 20 + /// @return token1 The address at byte 23 + function toPool(bytes calldata _bytes) internal pure returns (address token0, uint24 fee, address token1) { + if (_bytes.length < Constants.V3_POP_OFFSET) revert SliceOutOfBounds(); + assembly { + let firstWord := calldataload(_bytes.offset) + token0 := shr(96, firstWord) + fee := and(shr(72, firstWord), 0xffffff) + token1 := shr(96, calldataload(add(_bytes.offset, 23))) } + } - /// @notice Decode the `_arg`-th element in `_bytes` as a dynamic array - /// @dev The decoding of `length` and `offset` is universal, - /// whereas the type declaration of `res` instructs the compiler how to read it. - /// @param _bytes The input bytes string to slice - /// @param _arg The index of the argument to extract - /// @return length Length of the array - /// @return offset Pointer to the data part of the array - function toLengthOffset(bytes calldata _bytes, uint256 _arg) - internal - pure - returns (uint256 length, uint256 offset) - { - uint256 relativeOffset; - assembly { - // The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer. - // shl(5, x) is equivalent to mul(32, x) - let lengthPtr := add(_bytes.offset, calldataload(add(_bytes.offset, shl(5, _arg)))) - length := calldataload(lengthPtr) - offset := add(lengthPtr, 0x20) - relativeOffset := sub(offset, _bytes.offset) - } - if (_bytes.length < length + relativeOffset) revert SliceOutOfBounds(); + /// @notice Decode the `_arg`-th element in `_bytes` as a dynamic array + /// @dev The decoding of `length` and `offset` is universal, + /// whereas the type declaration of `res` instructs the compiler how to read it. + /// @param _bytes The input bytes string to slice + /// @param _arg The index of the argument to extract + /// @return length Length of the array + /// @return offset Pointer to the data part of the array + function toLengthOffset(bytes calldata _bytes, uint256 _arg) internal pure returns (uint256 length, uint256 offset) { + uint256 relativeOffset; + assembly { + // The offset of the `_arg`-th element is `32 * arg`, which stores the offset of the length pointer. + // shl(5, x) is equivalent to mul(32, x) + let lengthPtr := add(_bytes.offset, calldataload(add(_bytes.offset, shl(5, _arg)))) + length := calldataload(lengthPtr) + offset := add(lengthPtr, 0x20) + relativeOffset := sub(offset, _bytes.offset) } + if (_bytes.length < length + relativeOffset) revert SliceOutOfBounds(); + } - /// @notice Decode the `_arg`-th element in `_bytes` as `bytes` - /// @param _bytes The input bytes string to extract a bytes string from - /// @param _arg The index of the argument to extract - function toBytes(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes calldata res) { - (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); - assembly { - res.length := length - res.offset := offset - } + /// @notice Decode the `_arg`-th element in `_bytes` as `bytes` + /// @param _bytes The input bytes string to extract a bytes string from + /// @param _arg The index of the argument to extract + function toBytes(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes calldata res) { + (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); + assembly { + res.length := length + res.offset := offset } + } - /// @notice Decode the `_arg`-th element in `_bytes` as `address[]` - /// @param _bytes The input bytes string to extract an address array from - /// @param _arg The index of the argument to extract - function toAddressArray(bytes calldata _bytes, uint256 _arg) internal pure returns (address[] calldata res) { - (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); - assembly { - res.length := length - res.offset := offset - } + /// @notice Decode the `_arg`-th element in `_bytes` as `address[]` + /// @param _bytes The input bytes string to extract an address array from + /// @param _arg The index of the argument to extract + function toAddressArray(bytes calldata _bytes, uint256 _arg) internal pure returns (address[] calldata res) { + (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); + assembly { + res.length := length + res.offset := offset } + } - /// @notice Decode the `_arg`-th element in `_bytes` as `bytes[]` - /// @param _bytes The input bytes string to extract a bytes array from - /// @param _arg The index of the argument to extract - function toBytesArray(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes[] calldata res) { - (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); - assembly { - res.length := length - res.offset := offset - } + /// @notice Decode the `_arg`-th element in `_bytes` as `bytes[]` + /// @param _bytes The input bytes string to extract a bytes array from + /// @param _arg The index of the argument to extract + function toBytesArray(bytes calldata _bytes, uint256 _arg) internal pure returns (bytes[] calldata res) { + (uint256 length, uint256 offset) = toLengthOffset(_bytes, _arg); + assembly { + res.length := length + res.offset := offset } + } } diff --git a/src/aggregate-router/modules/uniswap/v3/V3Path.sol b/src/aggregate-router/modules/uniswap/v3/V3Path.sol index f0ec045..0d33fd9 100644 --- a/src/aggregate-router/modules/uniswap/v3/V3Path.sol +++ b/src/aggregate-router/modules/uniswap/v3/V3Path.sol @@ -1,43 +1,43 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.6.0; -import {BytesLib} from './BytesLib.sol'; -import {Constants} from '../../../libraries/Constants.sol'; +import { BytesLib } from "./BytesLib.sol"; +import { Constants } from "../../../libraries/Constants.sol"; /// @title Functions for manipulating path data for multihop swaps library V3Path { - using BytesLib for bytes; + using BytesLib for bytes; - /// @notice Returns true iff the path contains two or more pools - /// @param path The encoded swap path - /// @return True if path contains two or more pools, otherwise false - function hasMultiplePools(bytes calldata path) internal pure returns (bool) { - return path.length >= Constants.MULTIPLE_V3_POOLS_MIN_LENGTH; - } + /// @notice Returns true iff the path contains two or more pools + /// @param path The encoded swap path + /// @return True if path contains two or more pools, otherwise false + function hasMultiplePools(bytes calldata path) internal pure returns (bool) { + return path.length >= Constants.MULTIPLE_V3_POOLS_MIN_LENGTH; + } - /// @notice Decodes the first pool in path - /// @param path The bytes encoded swap path - /// @return tokenA The first token of the given pool - /// @return fee The fee level of the pool - /// @return tokenB The second token of the given pool - function decodeFirstPool(bytes calldata path) internal pure returns (address, uint24, address) { - return path.toPool(); - } + /// @notice Decodes the first pool in path + /// @param path The bytes encoded swap path + /// @return tokenA The first token of the given pool + /// @return fee The fee level of the pool + /// @return tokenB The second token of the given pool + function decodeFirstPool(bytes calldata path) internal pure returns (address, uint24, address) { + return path.toPool(); + } - /// @notice Gets the segment corresponding to the first pool in the path - /// @param path The bytes encoded swap path - /// @return The segment containing all data necessary to target the first pool in the path - function getFirstPool(bytes calldata path) internal pure returns (bytes calldata) { - return path[:Constants.V3_POP_OFFSET]; - } + /// @notice Gets the segment corresponding to the first pool in the path + /// @param path The bytes encoded swap path + /// @return The segment containing all data necessary to target the first pool in the path + function getFirstPool(bytes calldata path) internal pure returns (bytes calldata) { + return path[:Constants.V3_POP_OFFSET]; + } - function decodeFirstToken(bytes calldata path) internal pure returns (address tokenA) { - tokenA = path.toAddress(); - } + function decodeFirstToken(bytes calldata path) internal pure returns (address tokenA) { + tokenA = path.toAddress(); + } - /// @notice Skips a token + fee element - /// @param path The swap path - function skipToken(bytes calldata path) internal pure returns (bytes calldata) { - return path[Constants.NEXT_V3_POOL_OFFSET:]; - } + /// @notice Skips a token + fee element + /// @param path The swap path + function skipToken(bytes calldata path) internal pure returns (bytes calldata) { + return path[Constants.NEXT_V3_POOL_OFFSET:]; + } } diff --git a/src/aggregate-router/modules/uniswap/v3/V3SwapRouter.sol b/src/aggregate-router/modules/uniswap/v3/V3SwapRouter.sol index 7708f3a..f7da9da 100644 --- a/src/aggregate-router/modules/uniswap/v3/V3SwapRouter.sol +++ b/src/aggregate-router/modules/uniswap/v3/V3SwapRouter.sol @@ -1,177 +1,170 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import {V3Path} from './V3Path.sol'; -import {BytesLib} from './BytesLib.sol'; -import {SafeCast} from '@uniswap/v3-core/contracts/libraries/SafeCast.sol'; -import {IUniswapV3Pool} from '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol'; -import {IUniswapV3SwapCallback} from '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol'; -import {Constants} from '../../../libraries/Constants.sol'; -import {Permit2Payments} from '../../Permit2Payments.sol'; -import {UniswapImmutables} from '../UniswapImmutables.sol'; -import {Constants} from '../../../libraries/Constants.sol'; -import {ERC20} from 'solmate/src/tokens/ERC20.sol'; +import { V3Path } from "./V3Path.sol"; +import { BytesLib } from "./BytesLib.sol"; +import { SafeCast } from "@uniswap/v3-core/contracts/libraries/SafeCast.sol"; +import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; +import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; +import { Constants } from "../../../libraries/Constants.sol"; +import { Permit2Payments } from "../../Permit2Payments.sol"; +import { UniswapImmutables } from "../UniswapImmutables.sol"; +import { Constants } from "../../../libraries/Constants.sol"; +import { ERC20 } from "solmate/src/tokens/ERC20.sol"; /// @title Router for Uniswap v3 Trades abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3SwapCallback { - using V3Path for bytes; - using BytesLib for bytes; - using SafeCast for uint256; - - error V3InvalidSwap(); - error V3TooLittleReceived(); - error V3TooMuchRequested(); - error V3InvalidAmountOut(); - error V3InvalidCaller(); - - /// @dev Used as the placeholder value for maxAmountIn, because the computed amount in for an exact output swap - /// can never actually be this value - uint256 private constant DEFAULT_MAX_AMOUNT_IN = type(uint256).max; - - /// @dev Transient storage variable used for checking slippage - uint256 private maxAmountInCached = DEFAULT_MAX_AMOUNT_IN; - - /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) - uint160 internal constant MIN_SQRT_RATIO = 4295128739; - - /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) - uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - - function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external { - if (amount0Delta <= 0 && amount1Delta <= 0) revert V3InvalidSwap(); // swaps entirely within 0-liquidity regions are not supported - (, address payer) = abi.decode(data, (bytes, address)); - bytes calldata path = data.toBytes(0); - - // because exact output swaps are executed in reverse order, in this case tokenOut is actually tokenIn - (address tokenIn, uint24 fee, address tokenOut) = path.decodeFirstPool(); - - if (computePoolAddress(tokenIn, tokenOut, fee) != msg.sender) revert V3InvalidCaller(); - - (bool isExactInput, uint256 amountToPay) = - amount0Delta > 0 ? (tokenIn < tokenOut, uint256(amount0Delta)) : (tokenOut < tokenIn, uint256(amount1Delta)); - - if (isExactInput) { - // Pay the pool (msg.sender) - payOrPermit2Transfer(tokenIn, payer, msg.sender, amountToPay); - } else { - // either initiate the next swap or pay - if (path.hasMultiplePools()) { - // this is an intermediate step so the payer is actually this contract - path = path.skipToken(); - _swap(-amountToPay.toInt256(), msg.sender, path, payer, false); - } else { - if (amountToPay > maxAmountInCached) revert V3TooMuchRequested(); - // note that because exact output swaps are executed in reverse order, tokenOut is actually tokenIn - payOrPermit2Transfer(tokenOut, payer, msg.sender, amountToPay); - } - } + using V3Path for bytes; + using BytesLib for bytes; + using SafeCast for uint256; + + error V3InvalidSwap(); + error V3TooLittleReceived(); + error V3TooMuchRequested(); + error V3InvalidAmountOut(); + error V3InvalidCaller(); + + /// @dev Used as the placeholder value for maxAmountIn, because the computed amount in for an exact output swap + /// can never actually be this value + uint256 private constant DEFAULT_MAX_AMOUNT_IN = type(uint256).max; + + /// @dev Transient storage variable used for checking slippage + uint256 private maxAmountInCached = DEFAULT_MAX_AMOUNT_IN; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external { + if (amount0Delta <= 0 && amount1Delta <= 0) revert V3InvalidSwap(); // swaps entirely within 0-liquidity regions are not supported + (, address payer) = abi.decode(data, (bytes, address)); + bytes calldata path = data.toBytes(0); + + // because exact output swaps are executed in reverse order, in this case tokenOut is actually tokenIn + (address tokenIn, uint24 fee, address tokenOut) = path.decodeFirstPool(); + + if (computePoolAddress(tokenIn, tokenOut, fee) != msg.sender) revert V3InvalidCaller(); + + (bool isExactInput, uint256 amountToPay) = + amount0Delta > 0 ? (tokenIn < tokenOut, uint256(amount0Delta)) : (tokenOut < tokenIn, uint256(amount1Delta)); + + if (isExactInput) { + // Pay the pool (msg.sender) + payOrPermit2Transfer(tokenIn, payer, msg.sender, amountToPay); + } else { + // either initiate the next swap or pay + if (path.hasMultiplePools()) { + // this is an intermediate step so the payer is actually this contract + path = path.skipToken(); + _swap(-amountToPay.toInt256(), msg.sender, path, payer, false); + } else { + if (amountToPay > maxAmountInCached) revert V3TooMuchRequested(); + // note that because exact output swaps are executed in reverse order, tokenOut is actually tokenIn + payOrPermit2Transfer(tokenOut, payer, msg.sender, amountToPay); + } } - - /// @notice Performs a Uniswap v3 exact input swap - /// @param recipient The recipient of the output tokens - /// @param amountIn The amount of input tokens for the trade - /// @param amountOutMinimum The minimum desired amount of output tokens - /// @param path The path of the trade as a bytes string - /// @param payer The address that will be paying the input - function v3SwapExactInput( - address recipient, - uint256 amountIn, - uint256 amountOutMinimum, - bytes calldata path, - address payer - ) internal { - // use amountIn == Constants.CONTRACT_BALANCE as a flag to swap the entire balance of the contract - if (amountIn == Constants.CONTRACT_BALANCE) { - address tokenIn = path.decodeFirstToken(); - amountIn = ERC20(tokenIn).balanceOf(address(this)); - } - - uint256 amountOut; - while (true) { - bool hasMultiplePools = path.hasMultiplePools(); - - // the outputs of prior swaps become the inputs to subsequent ones - (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) = _swap( - amountIn.toInt256(), - hasMultiplePools ? address(this) : recipient, // for intermediate swaps, this contract custodies - path.getFirstPool(), // only the first pool is needed - payer, // for intermediate swaps, this contract custodies - true - ); - - amountIn = uint256(-(zeroForOne ? amount1Delta : amount0Delta)); - - // decide whether to continue or terminate - if (hasMultiplePools) { - payer = address(this); - path = path.skipToken(); - } else { - amountOut = amountIn; - break; - } - } - - if (amountOut < amountOutMinimum) revert V3TooLittleReceived(); - } - - /// @notice Performs a Uniswap v3 exact output swap - /// @param recipient The recipient of the output tokens - /// @param amountOut The amount of output tokens to receive for the trade - /// @param amountInMaximum The maximum desired amount of input tokens - /// @param path The path of the trade as a bytes string - /// @param payer The address that will be paying the input - function v3SwapExactOutput( - address recipient, - uint256 amountOut, - uint256 amountInMaximum, - bytes calldata path, - address payer - ) internal { - maxAmountInCached = amountInMaximum; - (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) = - _swap(-amountOut.toInt256(), recipient, path, payer, false); - - uint256 amountOutReceived = zeroForOne ? uint256(-amount1Delta) : uint256(-amount0Delta); - - if (amountOutReceived != amountOut) revert V3InvalidAmountOut(); - - maxAmountInCached = DEFAULT_MAX_AMOUNT_IN; + } + + /// @notice Performs a Uniswap v3 exact input swap + /// @param recipient The recipient of the output tokens + /// @param amountIn The amount of input tokens for the trade + /// @param amountOutMinimum The minimum desired amount of output tokens + /// @param path The path of the trade as a bytes string + /// @param payer The address that will be paying the input + function v3SwapExactInput( + address recipient, + uint256 amountIn, + uint256 amountOutMinimum, + bytes calldata path, + address payer + ) internal { + // use amountIn == Constants.CONTRACT_BALANCE as a flag to swap the entire balance of the contract + if (amountIn == Constants.CONTRACT_BALANCE) { + address tokenIn = path.decodeFirstToken(); + amountIn = ERC20(tokenIn).balanceOf(address(this)); } - /// @dev Performs a single swap for both exactIn and exactOut - /// For exactIn, `amount` is `amountIn`. For exactOut, `amount` is `-amountOut` - function _swap(int256 amount, address recipient, bytes calldata path, address payer, bool isExactIn) - private - returns (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) - { - (address tokenIn, uint24 fee, address tokenOut) = path.decodeFirstPool(); - - zeroForOne = isExactIn ? tokenIn < tokenOut : tokenOut < tokenIn; - - (amount0Delta, amount1Delta) = IUniswapV3Pool(computePoolAddress(tokenIn, tokenOut, fee)).swap( - recipient, - zeroForOne, - amount, - (zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1), - abi.encode(path, payer) - ); + uint256 amountOut; + while (true) { + bool hasMultiplePools = path.hasMultiplePools(); + + // the outputs of prior swaps become the inputs to subsequent ones + (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) = _swap( + amountIn.toInt256(), + hasMultiplePools ? address(this) : recipient, // for intermediate swaps, this contract custodies + path.getFirstPool(), // only the first pool is needed + payer, // for intermediate swaps, this contract custodies + true + ); + + amountIn = uint256(-(zeroForOne ? amount1Delta : amount0Delta)); + + // decide whether to continue or terminate + if (hasMultiplePools) { + payer = address(this); + path = path.skipToken(); + } else { + amountOut = amountIn; + break; + } } - function computePoolAddress(address tokenA, address tokenB, uint24 fee) private view returns (address pool) { - if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); - pool = address( - uint160( - uint256( - keccak256( - abi.encodePacked( - hex'ff', - UNISWAP_V3_FACTORY, - keccak256(abi.encode(tokenA, tokenB, fee)), - UNISWAP_V3_POOL_INIT_CODE_HASH - ) - ) - ) + if (amountOut < amountOutMinimum) revert V3TooLittleReceived(); + } + + /// @notice Performs a Uniswap v3 exact output swap + /// @param recipient The recipient of the output tokens + /// @param amountOut The amount of output tokens to receive for the trade + /// @param amountInMaximum The maximum desired amount of input tokens + /// @param path The path of the trade as a bytes string + /// @param payer The address that will be paying the input + function v3SwapExactOutput( + address recipient, + uint256 amountOut, + uint256 amountInMaximum, + bytes calldata path, + address payer + ) internal { + maxAmountInCached = amountInMaximum; + (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) = + _swap(-amountOut.toInt256(), recipient, path, payer, false); + + uint256 amountOutReceived = zeroForOne ? uint256(-amount1Delta) : uint256(-amount0Delta); + + if (amountOutReceived != amountOut) revert V3InvalidAmountOut(); + + maxAmountInCached = DEFAULT_MAX_AMOUNT_IN; + } + + /// @dev Performs a single swap for both exactIn and exactOut + /// For exactIn, `amount` is `amountIn`. For exactOut, `amount` is `-amountOut` + function _swap(int256 amount, address recipient, bytes calldata path, address payer, bool isExactIn) + private + returns (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) + { + (address tokenIn, uint24 fee, address tokenOut) = path.decodeFirstPool(); + + zeroForOne = isExactIn ? tokenIn < tokenOut : tokenOut < tokenIn; + + (amount0Delta, amount1Delta) = IUniswapV3Pool(computePoolAddress(tokenIn, tokenOut, fee)).swap( + recipient, zeroForOne, amount, (zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1), abi.encode(path, payer) + ); + } + + function computePoolAddress(address tokenA, address tokenB, uint24 fee) private view returns (address pool) { + if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA); + pool = address( + uint160( + uint256( + keccak256( + abi.encodePacked( + hex"ff", UNISWAP_V3_FACTORY, keccak256(abi.encode(tokenA, tokenB, fee)), UNISWAP_V3_POOL_INIT_CODE_HASH ) - ); - } + ) + ) + ) + ); + } } From 7bd6cd28aa919af086f41093db797e286aa8ae7d Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 14:49:34 +0700 Subject: [PATCH 02/22] forge install: openzeppelin-contracts v5.0.2 --- .gitmodules | 5 ++++- lib/openzeppelin-contracts | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) create mode 160000 lib/openzeppelin-contracts diff --git a/.gitmodules b/.gitmodules index 5cf20a5..a36c545 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,4 +2,7 @@ path = lib/foundry-deployment-kit url = https://github.com/axieinfinity/foundry-deployment-kit branch = v0.2.0 - \ No newline at end of file + +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 From bdc260565afc2bfc9ee62b802bc57de28435c30e Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 15:02:49 +0700 Subject: [PATCH 03/22] forge install: solmate 8d910d876f51c3b2585c9109409d601f600e68e1 --- .gitmodules | 3 +++ lib/solmate | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/solmate diff --git a/.gitmodules b/.gitmodules index a36c545..72a84ff 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,6 @@ [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts +[submodule "lib/solmate"] + path = lib/solmate + url = https://github.com/transmissions11/solmate diff --git a/lib/solmate b/lib/solmate new file mode 160000 index 0000000..8d910d8 --- /dev/null +++ b/lib/solmate @@ -0,0 +1 @@ +Subproject commit 8d910d876f51c3b2585c9109409d601f600e68e1 From b6613082f54240b24e4e680dccc9c1d81bb83aa6 Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 15:09:32 +0700 Subject: [PATCH 04/22] forge install: katana-v3-contracts dd827391e22db58687b27bfa3622f40ccf66661e --- .gitmodules | 3 +++ lib/katana-v3-contracts | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/katana-v3-contracts diff --git a/.gitmodules b/.gitmodules index 72a84ff..df9a913 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,3 +9,6 @@ [submodule "lib/solmate"] path = lib/solmate url = https://github.com/transmissions11/solmate +[submodule "lib/katana-v3-contracts"] + path = lib/katana-v3-contracts + url = https://github.com/ronin-chain/katana-v3-contracts diff --git a/lib/katana-v3-contracts b/lib/katana-v3-contracts new file mode 160000 index 0000000..dd82739 --- /dev/null +++ b/lib/katana-v3-contracts @@ -0,0 +1 @@ +Subproject commit dd827391e22db58687b27bfa3622f40ccf66661e From 7068ac5d9c6484d04174e2ed6ffd812865a788ff Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 15:37:52 +0700 Subject: [PATCH 05/22] BREAKING CHANGE: rename Uniswap to Katana --- remappings.txt | 4 +- src/aggregate-router/UniversalRouter.sol | 6 +-- src/aggregate-router/base/Dispatcher.sol | 6 +-- .../interfaces/external/IKatanaV2Pair.sol | 52 +++++++++++++++++++ .../modules/katana/KatanaImmutables.sol | 30 +++++++++++ .../v2/KatanaV2Library.sol} | 8 +-- .../{uniswap => katana}/v2/V2SwapRouter.sol | 28 +++++----- .../{uniswap => katana}/v3/BytesLib.sol | 0 .../modules/{uniswap => katana}/v3/V3Path.sol | 0 .../{uniswap => katana}/v3/V3SwapRouter.sol | 22 ++++---- .../modules/uniswap/UniswapImmutables.sol | 30 ----------- 11 files changed, 120 insertions(+), 66 deletions(-) create mode 100644 src/aggregate-router/interfaces/external/IKatanaV2Pair.sol create mode 100644 src/aggregate-router/modules/katana/KatanaImmutables.sol rename src/aggregate-router/modules/{uniswap/v2/UniswapV2Library.sol => katana/v2/KatanaV2Library.sol} (96%) rename src/aggregate-router/modules/{uniswap => katana}/v2/V2SwapRouter.sol (72%) rename src/aggregate-router/modules/{uniswap => katana}/v3/BytesLib.sol (100%) rename src/aggregate-router/modules/{uniswap => katana}/v3/V3Path.sol (100%) rename src/aggregate-router/modules/{uniswap => katana}/v3/V3SwapRouter.sol (87%) delete mode 100644 src/aggregate-router/modules/uniswap/UniswapImmutables.sol diff --git a/remappings.txt b/remappings.txt index f3949fc..657e251 100644 --- a/remappings.txt +++ b/remappings.txt @@ -2,4 +2,6 @@ @contract-libs/=lib/foundry-deployment-kit/lib/contract-libs/src/ forge-std/=lib/foundry-deployment-kit/lib/forge-std/src/ @solady/=lib/foundry-deployment-kit/lib/solady/src/ -@openzeppelin/contracts/=lib/foundry-deployment-kit/lib/openzeppelin-contracts/contracts/ \ No newline at end of file +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +solmate/=lib/solmate/ +@katana/v3-contracts/=lib/katana-v3-contracts/src/ \ No newline at end of file diff --git a/src/aggregate-router/UniversalRouter.sol b/src/aggregate-router/UniversalRouter.sol index 164167e..66c705d 100644 --- a/src/aggregate-router/UniversalRouter.sol +++ b/src/aggregate-router/UniversalRouter.sol @@ -7,7 +7,7 @@ import { RewardsCollector } from "./base/RewardsCollector.sol"; import { RouterParameters } from "./base/RouterImmutables.sol"; import { PaymentsImmutables, PaymentsParameters } from "./modules/PaymentsImmutables.sol"; import { NFTImmutables, NFTParameters } from "./modules/NFTImmutables.sol"; -import { UniswapImmutables, UniswapParameters } from "./modules/uniswap/UniswapImmutables.sol"; +import { KatanaImmutables, KatanaParameters } from "./modules/katana/KatanaImmutables.sol"; import { Commands } from "./libraries/Commands.sol"; import { IUniversalRouter } from "./interfaces/IUniversalRouter.sol"; @@ -18,8 +18,8 @@ contract UniversalRouter is IUniversalRouter, Dispatcher, RewardsCollector { } constructor(RouterParameters memory params) - UniswapImmutables( - UniswapParameters(params.v2Factory, params.v3Factory, params.pairInitCodeHash, params.poolInitCodeHash) + KatanaImmutables( + KatanaParameters(params.v2Factory, params.v3Factory, params.pairInitCodeHash, params.poolInitCodeHash) ) PaymentsImmutables(PaymentsParameters(params.permit2, params.weth9, params.openseaConduit, params.sudoswap)) NFTImmutables( diff --git a/src/aggregate-router/base/Dispatcher.sol b/src/aggregate-router/base/Dispatcher.sol index d8e9ee7..443ad74 100644 --- a/src/aggregate-router/base/Dispatcher.sol +++ b/src/aggregate-router/base/Dispatcher.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import { V2SwapRouter } from "../modules/uniswap/v2/V2SwapRouter.sol"; -import { V3SwapRouter } from "../modules/uniswap/v3/V3SwapRouter.sol"; -import { BytesLib } from "../modules/uniswap/v3/BytesLib.sol"; +import { V2SwapRouter } from "../modules/katana/v2/V2SwapRouter.sol"; +import { V3SwapRouter } from "../modules/katana/v3/V3SwapRouter.sol"; +import { BytesLib } from "../modules/katana/v3/BytesLib.sol"; import { Payments } from "../modules/Payments.sol"; import { PaymentsImmutables } from "../modules/PaymentsImmutables.sol"; import { NFTImmutables } from "../modules/NFTImmutables.sol"; diff --git a/src/aggregate-router/interfaces/external/IKatanaV2Pair.sol b/src/aggregate-router/interfaces/external/IKatanaV2Pair.sol new file mode 100644 index 0000000..ef427ab --- /dev/null +++ b/src/aggregate-router/interfaces/external/IKatanaV2Pair.sol @@ -0,0 +1,52 @@ +pragma solidity >=0.5.0; + +interface IKatanaV2Pair { + event Approval(address indexed owner, address indexed spender, uint value); + event Transfer(address indexed from, address indexed to, uint value); + + function name() external pure returns (string memory); + function symbol() external pure returns (string memory); + function decimals() external pure returns (uint8); + function totalSupply() external view returns (uint); + function balanceOf(address owner) external view returns (uint); + function allowance(address owner, address spender) external view returns (uint); + + function approve(address spender, uint value) external returns (bool); + function transfer(address to, uint value) external returns (bool); + function transferFrom(address from, address to, uint value) external returns (bool); + + function DOMAIN_SEPARATOR() external view returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); + function nonces(address owner) external view returns (uint); + + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; + + event Mint(address indexed sender, uint amount0, uint amount1); + event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); + event Swap( + address indexed sender, + uint amount0In, + uint amount1In, + uint amount0Out, + uint amount1Out, + address indexed to + ); + event Sync(uint112 reserve0, uint112 reserve1); + + function MINIMUM_LIQUIDITY() external pure returns (uint); + function factory() external view returns (address); + function token0() external view returns (address); + function token1() external view returns (address); + function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); + function price0CumulativeLast() external view returns (uint); + function price1CumulativeLast() external view returns (uint); + function kLast() external view returns (uint); + + function mint(address to) external returns (uint liquidity); + function burn(address to) external returns (uint amount0, uint amount1); + function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external; + function skim(address to) external; + function sync() external; + + function initialize(address, address) external; +} \ No newline at end of file diff --git a/src/aggregate-router/modules/katana/KatanaImmutables.sol b/src/aggregate-router/modules/katana/KatanaImmutables.sol new file mode 100644 index 0000000..3f9a145 --- /dev/null +++ b/src/aggregate-router/modules/katana/KatanaImmutables.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.17; + +struct KatanaParameters { + address v2Factory; + address v3Factory; + bytes32 pairInitCodeHash; + bytes32 poolInitCodeHash; +} + +contract KatanaImmutables { + /// @dev The address of KatanaV2Factory + address internal immutable KATANA_V2_FACTORY; + + /// @dev The KatanaV2Pair initcodehash + bytes32 internal immutable KATANA_V2_PAIR_INIT_CODE_HASH; + + /// @dev The address of KatanaV3Factory + address internal immutable KATANA_V3_FACTORY; + + /// @dev The KatanaV3Pool initcodehash + bytes32 internal immutable KATANA_V3_POOL_INIT_CODE_HASH; + + constructor(KatanaParameters memory params) { + KATANA_V2_FACTORY = params.v2Factory; + KATANA_V2_PAIR_INIT_CODE_HASH = params.pairInitCodeHash; + KATANA_V3_FACTORY = params.v3Factory; + KATANA_V3_POOL_INIT_CODE_HASH = params.poolInitCodeHash; + } +} diff --git a/src/aggregate-router/modules/uniswap/v2/UniswapV2Library.sol b/src/aggregate-router/modules/katana/v2/KatanaV2Library.sol similarity index 96% rename from src/aggregate-router/modules/uniswap/v2/UniswapV2Library.sol rename to src/aggregate-router/modules/katana/v2/KatanaV2Library.sol index 5e04452..dc8765b 100644 --- a/src/aggregate-router/modules/uniswap/v2/UniswapV2Library.sol +++ b/src/aggregate-router/modules/katana/v2/KatanaV2Library.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.0; -import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; +import { IKatanaV2Pair } from "../../../interfaces/external/IKatanaV2Pair.sol"; -/// @title Uniswap v2 Helper Library +/// @title Katana v2 Helper Library /// @notice Calculates the recipient address for a command -library UniswapV2Library { +library KatanaV2Library { error InvalidReserves(); error InvalidPath(); @@ -76,7 +76,7 @@ library UniswapV2Library { { address token0; (pair, token0) = pairAndToken0For(factory, initCodeHash, tokenA, tokenB); - (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); + (uint256 reserve0, uint256 reserve1,) = IKatanaV2Pair(pair).getReserves(); (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0); } diff --git a/src/aggregate-router/modules/uniswap/v2/V2SwapRouter.sol b/src/aggregate-router/modules/katana/v2/V2SwapRouter.sol similarity index 72% rename from src/aggregate-router/modules/uniswap/v2/V2SwapRouter.sol rename to src/aggregate-router/modules/katana/v2/V2SwapRouter.sol index bb0bcdd..0fdbb1c 100644 --- a/src/aggregate-router/modules/uniswap/v2/V2SwapRouter.sol +++ b/src/aggregate-router/modules/katana/v2/V2SwapRouter.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.17; -import { IUniswapV2Pair } from "@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol"; -import { UniswapV2Library } from "./UniswapV2Library.sol"; -import { UniswapImmutables } from "../UniswapImmutables.sol"; +import { IKatanaV2Pair } from "../../../interfaces/external/IKatanaV2Pair.sol"; +import { KatanaV2Library } from "./KatanaV2Library.sol"; +import { KatanaImmutables } from "../KatanaImmutables.sol"; import { Payments } from "../../Payments.sol"; import { Permit2Payments } from "../../Permit2Payments.sol"; import { Constants } from "../../../libraries/Constants.sol"; import { ERC20 } from "solmate/src/tokens/ERC20.sol"; -/// @title Router for Uniswap v2 Trades -abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments { +/// @title Router for Katana v2 Trades +abstract contract V2SwapRouter is KatanaImmutables, Permit2Payments { error V2TooLittleReceived(); error V2TooMuchRequested(); error V2InvalidPath(); @@ -20,28 +20,28 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments { if (path.length < 2) revert V2InvalidPath(); // cached to save on duplicate operations - (address token0,) = UniswapV2Library.sortTokens(path[0], path[1]); + (address token0,) = KatanaV2Library.sortTokens(path[0], path[1]); uint256 finalPairIndex = path.length - 1; uint256 penultimatePairIndex = finalPairIndex - 1; for (uint256 i; i < finalPairIndex; i++) { (address input, address output) = (path[i], path[i + 1]); - (uint256 reserve0, uint256 reserve1,) = IUniswapV2Pair(pair).getReserves(); + (uint256 reserve0, uint256 reserve1,) = IKatanaV2Pair(pair).getReserves(); (uint256 reserveInput, uint256 reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0); uint256 amountInput = ERC20(input).balanceOf(pair) - reserveInput; - uint256 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput); + uint256 amountOutput = KatanaV2Library.getAmountOut(amountInput, reserveInput, reserveOutput); (uint256 amount0Out, uint256 amount1Out) = input == token0 ? (uint256(0), amountOutput) : (amountOutput, uint256(0)); address nextPair; (nextPair, token0) = i < penultimatePairIndex - ? UniswapV2Library.pairAndToken0For(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, output, path[i + 2]) + ? KatanaV2Library.pairAndToken0For(KATANA_V2_FACTORY, KATANA_V2_PAIR_INIT_CODE_HASH, output, path[i + 2]) : (recipient, address(0)); - IUniswapV2Pair(pair).swap(amount0Out, amount1Out, nextPair, new bytes(0)); + IKatanaV2Pair(pair).swap(amount0Out, amount1Out, nextPair, new bytes(0)); pair = nextPair; } } } - /// @notice Performs a Uniswap v2 exact input swap + /// @notice Performs a Katana v2 exact input swap /// @param recipient The recipient of the output tokens /// @param amountIn The amount of input tokens for the trade /// @param amountOutMinimum The minimum desired amount of output tokens @@ -54,7 +54,7 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments { address[] calldata path, address payer ) internal { - address firstPair = UniswapV2Library.pairFor(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, path[0], path[1]); + address firstPair = KatanaV2Library.pairFor(KATANA_V2_FACTORY, KATANA_V2_PAIR_INIT_CODE_HASH, path[0], path[1]); if ( amountIn != Constants.ALREADY_PAID // amountIn of 0 to signal that the pair already has the tokens ) { @@ -70,7 +70,7 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments { if (amountOut < amountOutMinimum) revert V2TooLittleReceived(); } - /// @notice Performs a Uniswap v2 exact output swap + /// @notice Performs a Katana v2 exact output swap /// @param recipient The recipient of the output tokens /// @param amountOut The amount of output tokens to receive for the trade /// @param amountInMaximum The maximum desired amount of input tokens @@ -84,7 +84,7 @@ abstract contract V2SwapRouter is UniswapImmutables, Permit2Payments { address payer ) internal { (uint256 amountIn, address firstPair) = - UniswapV2Library.getAmountInMultihop(UNISWAP_V2_FACTORY, UNISWAP_V2_PAIR_INIT_CODE_HASH, amountOut, path); + KatanaV2Library.getAmountInMultihop(KATANA_V2_FACTORY, KATANA_V2_PAIR_INIT_CODE_HASH, amountOut, path); if (amountIn > amountInMaximum) revert V2TooMuchRequested(); payOrPermit2Transfer(path[0], payer, firstPair, amountIn); diff --git a/src/aggregate-router/modules/uniswap/v3/BytesLib.sol b/src/aggregate-router/modules/katana/v3/BytesLib.sol similarity index 100% rename from src/aggregate-router/modules/uniswap/v3/BytesLib.sol rename to src/aggregate-router/modules/katana/v3/BytesLib.sol diff --git a/src/aggregate-router/modules/uniswap/v3/V3Path.sol b/src/aggregate-router/modules/katana/v3/V3Path.sol similarity index 100% rename from src/aggregate-router/modules/uniswap/v3/V3Path.sol rename to src/aggregate-router/modules/katana/v3/V3Path.sol diff --git a/src/aggregate-router/modules/uniswap/v3/V3SwapRouter.sol b/src/aggregate-router/modules/katana/v3/V3SwapRouter.sol similarity index 87% rename from src/aggregate-router/modules/uniswap/v3/V3SwapRouter.sol rename to src/aggregate-router/modules/katana/v3/V3SwapRouter.sol index f7da9da..17f36b0 100644 --- a/src/aggregate-router/modules/uniswap/v3/V3SwapRouter.sol +++ b/src/aggregate-router/modules/katana/v3/V3SwapRouter.sol @@ -3,17 +3,17 @@ pragma solidity ^0.8.17; import { V3Path } from "./V3Path.sol"; import { BytesLib } from "./BytesLib.sol"; -import { SafeCast } from "@uniswap/v3-core/contracts/libraries/SafeCast.sol"; -import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; -import { IUniswapV3SwapCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol"; +import { SafeCast } from "@katana/v3-contracts/core/libraries/SafeCast.sol"; +import { IKatanaV3Pool } from "@katana/v3-contracts/core/interfaces/IKatanaV3Pool.sol"; +import { IKatanaV3SwapCallback } from "@katana/v3-contracts/core/interfaces/callback/IKatanaV3SwapCallback.sol"; import { Constants } from "../../../libraries/Constants.sol"; import { Permit2Payments } from "../../Permit2Payments.sol"; -import { UniswapImmutables } from "../UniswapImmutables.sol"; +import { KatanaImmutables } from "../KatanaImmutables.sol"; import { Constants } from "../../../libraries/Constants.sol"; import { ERC20 } from "solmate/src/tokens/ERC20.sol"; -/// @title Router for Uniswap v3 Trades -abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3SwapCallback { +/// @title Router for Katana v3 Trades +abstract contract V3SwapRouter is KatanaImmutables, Permit2Payments, IKatanaV3SwapCallback { using V3Path for bytes; using BytesLib for bytes; using SafeCast for uint256; @@ -37,7 +37,7 @@ abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3 /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; - function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external { + function katanaV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external { if (amount0Delta <= 0 && amount1Delta <= 0) revert V3InvalidSwap(); // swaps entirely within 0-liquidity regions are not supported (, address payer) = abi.decode(data, (bytes, address)); bytes calldata path = data.toBytes(0); @@ -67,7 +67,7 @@ abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3 } } - /// @notice Performs a Uniswap v3 exact input swap + /// @notice Performs a Katana v3 exact input swap /// @param recipient The recipient of the output tokens /// @param amountIn The amount of input tokens for the trade /// @param amountOutMinimum The minimum desired amount of output tokens @@ -114,7 +114,7 @@ abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3 if (amountOut < amountOutMinimum) revert V3TooLittleReceived(); } - /// @notice Performs a Uniswap v3 exact output swap + /// @notice Performs a Katana v3 exact output swap /// @param recipient The recipient of the output tokens /// @param amountOut The amount of output tokens to receive for the trade /// @param amountInMaximum The maximum desired amount of input tokens @@ -148,7 +148,7 @@ abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3 zeroForOne = isExactIn ? tokenIn < tokenOut : tokenOut < tokenIn; - (amount0Delta, amount1Delta) = IUniswapV3Pool(computePoolAddress(tokenIn, tokenOut, fee)).swap( + (amount0Delta, amount1Delta) = IKatanaV3Pool(computePoolAddress(tokenIn, tokenOut, fee)).swap( recipient, zeroForOne, amount, (zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1), abi.encode(path, payer) ); } @@ -160,7 +160,7 @@ abstract contract V3SwapRouter is UniswapImmutables, Permit2Payments, IUniswapV3 uint256( keccak256( abi.encodePacked( - hex"ff", UNISWAP_V3_FACTORY, keccak256(abi.encode(tokenA, tokenB, fee)), UNISWAP_V3_POOL_INIT_CODE_HASH + hex"ff", KATANA_V3_FACTORY, keccak256(abi.encode(tokenA, tokenB, fee)), KATANA_V3_POOL_INIT_CODE_HASH ) ) ) diff --git a/src/aggregate-router/modules/uniswap/UniswapImmutables.sol b/src/aggregate-router/modules/uniswap/UniswapImmutables.sol deleted file mode 100644 index c1e7a30..0000000 --- a/src/aggregate-router/modules/uniswap/UniswapImmutables.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity ^0.8.17; - -struct UniswapParameters { - address v2Factory; - address v3Factory; - bytes32 pairInitCodeHash; - bytes32 poolInitCodeHash; -} - -contract UniswapImmutables { - /// @dev The address of UniswapV2Factory - address internal immutable UNISWAP_V2_FACTORY; - - /// @dev The UniswapV2Pair initcodehash - bytes32 internal immutable UNISWAP_V2_PAIR_INIT_CODE_HASH; - - /// @dev The address of UniswapV3Factory - address internal immutable UNISWAP_V3_FACTORY; - - /// @dev The UniswapV3Pool initcodehash - bytes32 internal immutable UNISWAP_V3_POOL_INIT_CODE_HASH; - - constructor(UniswapParameters memory params) { - UNISWAP_V2_FACTORY = params.v2Factory; - UNISWAP_V2_PAIR_INIT_CODE_HASH = params.pairInitCodeHash; - UNISWAP_V3_FACTORY = params.v3Factory; - UNISWAP_V3_POOL_INIT_CODE_HASH = params.poolInitCodeHash; - } -} From 8b2b17d928624f42aba2579654c8f1e8e45043d0 Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 16:30:36 +0700 Subject: [PATCH 06/22] forge install: permit2 a7cd186948b44f9096a35035226d7d70b9e24eaf --- .gitmodules | 3 +++ lib/permit2 | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/permit2 diff --git a/.gitmodules b/.gitmodules index df9a913..14b20da 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,3 +12,6 @@ [submodule "lib/katana-v3-contracts"] path = lib/katana-v3-contracts url = https://github.com/ronin-chain/katana-v3-contracts +[submodule "lib/permit2"] + path = lib/permit2 + url = https://github.com/Uniswap/permit2 diff --git a/lib/permit2 b/lib/permit2 new file mode 160000 index 0000000..a7cd186 --- /dev/null +++ b/lib/permit2 @@ -0,0 +1 @@ +Subproject commit a7cd186948b44f9096a35035226d7d70b9e24eaf From 7a29dc20a01685ae6bc4cbfc688d78d479fe1b58 Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 16:40:16 +0700 Subject: [PATCH 07/22] chore: remapping permit2 --- foundry.toml | 1 + remappings.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/foundry.toml b/foundry.toml index 8687c10..119adc5 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,6 +9,7 @@ ffi = true solc = '0.8.26' optimizer_runs = 1_000_000 +via_ir=true evm_version = 'istanbul' use_literal_content = true extra_output = ["devdoc", "userdoc", "storagelayout"] diff --git a/remappings.txt b/remappings.txt index 657e251..aa6782f 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ forge-std/=lib/foundry-deployment-kit/lib/forge-std/src/ @solady/=lib/foundry-deployment-kit/lib/solady/src/ @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ solmate/=lib/solmate/ +permit2/=lib/permit2/ @katana/v3-contracts/=lib/katana-v3-contracts/src/ \ No newline at end of file From 1a741904f85dc2ba62403f187c57ca8bd85dcf7a Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Wed, 17 Jul 2024 18:05:27 +0700 Subject: [PATCH 08/22] chore: modify ci --- .github/workflows/test.yml | 2 +- test/Dummy.t.sol | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 test/Dummy.t.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e8d435..95b0a51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,7 +23,7 @@ jobs: fail-fast: true name: Foundry project - runs-on: ubuntu-latest + runs-on: [self-hosted, gke] steps: - uses: actions/checkout@v3 with: diff --git a/test/Dummy.t.sol b/test/Dummy.t.sol new file mode 100644 index 0000000..5ea5648 --- /dev/null +++ b/test/Dummy.t.sol @@ -0,0 +1,9 @@ +pragma solidity ^0.8.0; + +import { Test } from "forge-std/Test.sol"; + +contract DummyTest is Test { + function test() public pure returns (bool) { + return true; + } +} \ No newline at end of file From 33cbd176a1c8de9ead4d3888a38302ae8df18d8e Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 10:14:13 +0700 Subject: [PATCH 09/22] get token for get private repo Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95b0a51..0945235 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,8 +25,16 @@ jobs: name: Foundry project runs-on: [self-hosted, gke] steps: + - id: 'gh-app' + name: 'Get Token' + uses: 'tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a' #v1.7.0 + with: + app_id: ${{ secrets.GH_APP_ID }} + private_key: ${{ secrets.GH_PRIVATE_KEY }} + - uses: actions/checkout@v3 with: + token: ${{ steps.gh-app.outputs.token }} submodules: recursive - name: Install Foundry From 6653a82324900a21f721ddbccebbe089d1e90661 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 10:34:16 +0700 Subject: [PATCH 10/22] Update test.yml Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .github/workflows/test.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0945235..95b0a51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,16 +25,8 @@ jobs: name: Foundry project runs-on: [self-hosted, gke] steps: - - id: 'gh-app' - name: 'Get Token' - uses: 'tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a' #v1.7.0 - with: - app_id: ${{ secrets.GH_APP_ID }} - private_key: ${{ secrets.GH_PRIVATE_KEY }} - - uses: actions/checkout@v3 with: - token: ${{ steps.gh-app.outputs.token }} submodules: recursive - name: Install Foundry From 34b1f0bc5e2f949fe10ce9781ebb64885f7d428f Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 10:37:20 +0700 Subject: [PATCH 11/22] Update .gitmodules Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 14b20da..7fa8822 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "lib/foundry-deployment-kit"] path = lib/foundry-deployment-kit - url = https://github.com/axieinfinity/foundry-deployment-kit + url = git@github.com/axieinfinity/foundry-deployment-kit.git branch = v0.2.0 [submodule "lib/openzeppelin-contracts"] @@ -11,7 +11,7 @@ url = https://github.com/transmissions11/solmate [submodule "lib/katana-v3-contracts"] path = lib/katana-v3-contracts - url = https://github.com/ronin-chain/katana-v3-contracts + url = git@github.com/ronin-chain/katana-v3-contracts.git [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 From f0b3c61c529aea306fa6be3e9ea5dc341b088cd2 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:32:11 +0700 Subject: [PATCH 12/22] Update .gitmodules Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .gitmodules | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 7fa8822..14b20da 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "lib/foundry-deployment-kit"] path = lib/foundry-deployment-kit - url = git@github.com/axieinfinity/foundry-deployment-kit.git + url = https://github.com/axieinfinity/foundry-deployment-kit branch = v0.2.0 [submodule "lib/openzeppelin-contracts"] @@ -11,7 +11,7 @@ url = https://github.com/transmissions11/solmate [submodule "lib/katana-v3-contracts"] path = lib/katana-v3-contracts - url = git@github.com/ronin-chain/katana-v3-contracts.git + url = https://github.com/ronin-chain/katana-v3-contracts [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 From 3fed02e2c35659154a9c5b86c0d8df9ac90a1256 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:39:19 +0700 Subject: [PATCH 13/22] Update .gitmodules Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 14b20da..746656f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,7 +11,7 @@ url = https://github.com/transmissions11/solmate [submodule "lib/katana-v3-contracts"] path = lib/katana-v3-contracts - url = https://github.com/ronin-chain/katana-v3-contracts + url = git@github.com/ronin-chain/katana-v3-contracts.git [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 From 725c6de81bd100de90812f6bc2ced6a3c33b661d Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:40:35 +0700 Subject: [PATCH 14/22] Update .gitmodules Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 746656f..14b20da 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,7 +11,7 @@ url = https://github.com/transmissions11/solmate [submodule "lib/katana-v3-contracts"] path = lib/katana-v3-contracts - url = git@github.com/ronin-chain/katana-v3-contracts.git + url = https://github.com/ronin-chain/katana-v3-contracts [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 From 91fce3778ae347fe04e3e57486a80131b37fa967 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:50:55 +0700 Subject: [PATCH 15/22] Update test.yml Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 95b0a51..770f7ba 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,8 +24,12 @@ jobs: name: Foundry project runs-on: [self-hosted, gke] + + permissions: + contents: read + steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive From c986d3532846a514a2a06729a0584ac9206d2a81 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:51:32 +0700 Subject: [PATCH 16/22] Update test.yml Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 770f7ba..48425bd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,7 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive + token: ${{ secrets.GITHUB_TOKEN }} - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 From 2ac97b7f25abd4b9d0bdc4a84fb5808581827279 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:56:58 +0700 Subject: [PATCH 17/22] Update test.yml Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .github/workflows/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 48425bd..8a718c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,15 +24,11 @@ jobs: name: Foundry project runs-on: [self-hosted, gke] - - permissions: - contents: read steps: - uses: actions/checkout@v4 with: submodules: recursive - token: ${{ secrets.GITHUB_TOKEN }} - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 From 199d38802a2359fb59106e8b3910371c7a6d2988 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:58:13 +0700 Subject: [PATCH 18/22] Update .gitmodules Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 14b20da..6b3c8af 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,7 +11,7 @@ url = https://github.com/transmissions11/solmate [submodule "lib/katana-v3-contracts"] path = lib/katana-v3-contracts - url = https://github.com/ronin-chain/katana-v3-contracts + url = git@github.com:ronin-chain/katana-v3-contracts.git [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 From b237d0b91a4285c6b7d6468e2639f95b8a34c23a Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:03:58 +0700 Subject: [PATCH 19/22] Update .gitmodules Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 6b3c8af..14b20da 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,7 +11,7 @@ url = https://github.com/transmissions11/solmate [submodule "lib/katana-v3-contracts"] path = lib/katana-v3-contracts - url = git@github.com:ronin-chain/katana-v3-contracts.git + url = https://github.com/ronin-chain/katana-v3-contracts [submodule "lib/permit2"] path = lib/permit2 url = https://github.com/Uniswap/permit2 From a887793fd1e59d21f19f61e98c74785f16aca849 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:04:38 +0700 Subject: [PATCH 20/22] Update test.yml Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .github/workflows/test.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a718c4..1c5d3a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,9 +26,17 @@ jobs: runs-on: [self-hosted, gke] steps: + - id: 'gh-app' + name: 'Get Token' + uses: 'tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a' #v1.7.0 + with: + app_id: ${{ secrets.GH_APP_ID }} + private_key: ${{ secrets.GH_PRIVATE_KEY }} + - uses: actions/checkout@v4 with: submodules: recursive + token: ${{ steps.gh-app.outputs.token }} - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 From b4ed7c377fae673f1b7bd6edd4522274b8ada441 Mon Sep 17 00:00:00 2001 From: qui-pham <100757140+qui-pham@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:29:52 +0700 Subject: [PATCH 21/22] Update test.yml Signed-off-by: qui-pham <100757140+qui-pham@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c5d3a8..b22794d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: - name: Run Forge build run: | forge --version - forge build --sizes + forge build id: build - name: Run Forge tests From 74916927e14cf1a493552b87726f1b968d533f58 Mon Sep 17 00:00:00 2001 From: Thai Xuan Dang Date: Fri, 19 Jul 2024 13:30:11 +0700 Subject: [PATCH 22/22] chore: rename UniversalRouter to AggregateRouter --- .../{UniversalRouter.sol => AggregateRouter.sol} | 6 +++--- .../{IUniversalRouter.sol => IAggregateRouter.sol} | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename src/aggregate-router/{UniversalRouter.sol => AggregateRouter.sol} (94%) rename src/aggregate-router/interfaces/{IUniversalRouter.sol => IAggregateRouter.sol} (95%) diff --git a/src/aggregate-router/UniversalRouter.sol b/src/aggregate-router/AggregateRouter.sol similarity index 94% rename from src/aggregate-router/UniversalRouter.sol rename to src/aggregate-router/AggregateRouter.sol index 66c705d..e021ee6 100644 --- a/src/aggregate-router/UniversalRouter.sol +++ b/src/aggregate-router/AggregateRouter.sol @@ -9,9 +9,9 @@ import { PaymentsImmutables, PaymentsParameters } from "./modules/PaymentsImmuta import { NFTImmutables, NFTParameters } from "./modules/NFTImmutables.sol"; import { KatanaImmutables, KatanaParameters } from "./modules/katana/KatanaImmutables.sol"; import { Commands } from "./libraries/Commands.sol"; -import { IUniversalRouter } from "./interfaces/IUniversalRouter.sol"; +import { IAggregateRouter } from "./interfaces/IAggregateRouter.sol"; -contract UniversalRouter is IUniversalRouter, Dispatcher, RewardsCollector { +contract AggregateRouter is IAggregateRouter, Dispatcher, RewardsCollector { modifier checkDeadline(uint256 deadline) { if (block.timestamp > deadline) revert TransactionDeadlinePassed(); _; @@ -41,7 +41,7 @@ contract UniversalRouter is IUniversalRouter, Dispatcher, RewardsCollector { ) { } - /// @inheritdoc IUniversalRouter + /// @inheritdoc IAggregateRouter function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable diff --git a/src/aggregate-router/interfaces/IUniversalRouter.sol b/src/aggregate-router/interfaces/IAggregateRouter.sol similarity index 95% rename from src/aggregate-router/interfaces/IUniversalRouter.sol rename to src/aggregate-router/interfaces/IAggregateRouter.sol index 5cb782f..f69eb64 100644 --- a/src/aggregate-router/interfaces/IUniversalRouter.sol +++ b/src/aggregate-router/interfaces/IAggregateRouter.sol @@ -5,7 +5,7 @@ import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Rec import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import { IRewardsCollector } from "./IRewardsCollector.sol"; -interface IUniversalRouter is IRewardsCollector, IERC721Receiver, IERC1155Receiver { +interface IAggregateRouter is IRewardsCollector, IERC721Receiver, IERC1155Receiver { /// @notice Thrown when a required command has failed error ExecutionFailed(uint256 commandIndex, bytes message);