diff --git a/contracts/base/Dispatcher.sol b/contracts/base/Dispatcher.sol index 28ee5916..01c9bb06 100644 --- a/contracts/base/Dispatcher.sol +++ b/contracts/base/Dispatcher.sol @@ -4,6 +4,7 @@ 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 {Calldata} from '../libraries/Calldata.sol'; import {Payments} from '../modules/Payments.sol'; import {RouterImmutables} from '../base/RouterImmutables.sol'; import {Callbacks} from '../base/Callbacks.sol'; @@ -19,6 +20,7 @@ import {ICryptoPunksMarket} from '../interfaces/external/ICryptoPunksMarket.sol' /// @notice Called by the UniversalRouter contract to efficiently decode and execute a singular command abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, LockAndMsgSender { using BytesLib for bytes; + using Calldata for bytes; error InvalidCommandType(uint256 commandType); error BuyPunkFailed(); @@ -43,84 +45,54 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, 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)) - } + address recipient = inputs.getAddress(); + uint256 amountIn = inputs.getUint256(0x20); + uint256 amountOutMin = inputs.getUint256(0x40); + // 0x60 offset is the path, decoded below bytes calldata path = inputs.toBytes(3); + bool payerIsUser = inputs.getBool(0x80); 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)) - } + address recipient = inputs.getAddress(); + uint256 amountOut = inputs.getUint256(0x20); + uint256 amountInMax = inputs.getUint256(0x40); bytes calldata path = inputs.toBytes(3); + // 0x60 offset is the path, decoded below + bool payerIsUser = inputs.getBool(0x80); 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)) - } + address token = inputs.getAddress(); + address recipient = inputs.getAddress(0x20); + uint160 amount = inputs.getUint160(0x40); permit2TransferFrom(token, lockedBy, map(recipient), amount); } else if (command == Commands.PERMIT2_PERMIT_BATCH) { - (IAllowanceTransfer.PermitBatch memory permitBatch,) = - abi.decode(inputs, (IAllowanceTransfer.PermitBatch, bytes)); + (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)) - } + address token = inputs.getAddress(); + address recipient = inputs.getAddress(0x20); + uint160 amountMin = inputs.getUint160(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)) - } + address token = inputs.getAddress(); + address recipient = inputs.getAddress(0x20); + uint256 value = inputs.getUint256(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)) - } + address token = inputs.getAddress(); + address recipient = inputs.getAddress(0x20); + uint256 bips = inputs.getUint256(0x40); Payments.payPortion(token, map(recipient), bips); } else { // placeholder area for command 0x07 @@ -130,34 +102,22 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, } 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 recipient = inputs.getAddress(); + uint256 amountIn = inputs.getUint256(0x20); + uint256 amountOutMin = inputs.getUint256(0x40); + // 0x60 offset is the path, decoded below address[] calldata path = inputs.toAddressArray(3); + bool payerIsUser = inputs.getBool(0x80); 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 recipient = inputs.getAddress(); + uint256 amountOut = inputs.getUint256(0x20); + uint256 amountInMax = inputs.getUint256(0x40); + // 0x60 offset is the path, decoded below address[] calldata path = inputs.toAddressArray(3); + bool payerIsUser = inputs.getBool(0x80); address payer = payerIsUser ? lockedBy : address(this); v2SwapExactOutput(map(recipient), amountOut, amountInMax, path, payer); } else if (command == Commands.PERMIT2_PERMIT) { @@ -170,36 +130,25 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, 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)) - } + address recipient = inputs.getAddress(); + uint256 amountMin = inputs.getUint256(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)) - } + address recipient = inputs.getAddress(); + uint256 amountMin = inputs.getUint256(0x20); Payments.unwrapWETH9(map(recipient), amountMin); } else if (command == Commands.PERMIT2_TRANSFER_FROM_BATCH) { - (IAllowanceTransfer.AllowanceTransferDetails[] memory batchDetails) = - abi.decode(inputs, (IAllowanceTransfer.AllowanceTransferDetails[])); + 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)) - } + address owner = inputs.getAddress(); + address token = inputs.getAddress(0x20); + uint256 minBalance = inputs.getUint256(0x40); success = (ERC20(token).balanceOf(owner) >= minBalance); if (!success) output = abi.encodePacked(BalanceTooLow.selector); } else { @@ -235,14 +184,9 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, (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)) - } + uint256 punkId = inputs.getUint256(); + address recipient = inputs.getAddress(0x20); + uint256 value = inputs.getUint256(0x40); (success, output) = CRYPTOPUNKS.call{value: value}( abi.encodeWithSelector(ICryptoPunksMarket.buyPunk.selector, punkId) ); @@ -250,40 +194,24 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, 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)) - } + address owner = inputs.getAddress(); + address token = inputs.getAddress(0x20); + uint256 id = inputs.getUint256(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)) - } + address owner = inputs.getAddress(); + address token = inputs.getAddress(0x20); + uint256 id = inputs.getUint256(0x40); + uint256 minBalance = inputs.getUint256(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)) - } + address token = inputs.getAddress(); + address recipient = inputs.getAddress(0x20); + uint256 id = inputs.getUint256(0x40); Payments.sweepERC721(token, map(recipient), id); } // 0x18 <= command < 0x1f @@ -304,16 +232,10 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, (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)) - } + address token = inputs.getAddress(); + address recipient = inputs.getAddress(0x20); + uint256 id = inputs.getUint256(0x40); + uint256 amount = inputs.getUint256(0x60); Payments.sweepERC1155(token, map(recipient), id, amount); } else if (command == Commands.ELEMENT_MARKET) { // equivalent: abi.decode(inputs, (uint256, bytes)) @@ -340,15 +262,12 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, } 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)); + (success, output) = (address(this)).call( + abi.encodeWithSelector(Dispatcher.execute.selector, _commands, _inputs) + ); } else if (command == Commands.APPROVE_ERC20) { - ERC20 token; - RouterImmutables.Spenders spender; - assembly { - token := calldataload(inputs.offset) - spender := calldataload(add(inputs.offset, 0x20)) - } + ERC20 token = ERC20(inputs.getAddress()); + RouterImmutables.Spenders spender = RouterImmutables.Spenders(inputs.getUint8(0x20)); Payments.approveERC20(token, spender); } else { // placeholder area for commands 0x22-0x3f @@ -367,21 +286,16 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, /// @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) - { + 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)) - } + // 0x00 and 0x20 offsets are value and data, above + address recipient = inputs.getAddress(0x40); + address token = inputs.getAddress(0x60); + uint256 id = inputs.getUint256(0x80); (success, output) = protocol.call{value: value}(data); if (success) ERC721(token).safeTransferFrom(address(this), map(recipient), id); } @@ -391,23 +305,17 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, /// @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) - { + 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)) - } + // 0x00 and 0x20 offsets are value and data, above + address recipient = inputs.getAddress(0x40); + address token = inputs.getAddress(0x60); + uint256 id = inputs.getUint256(0x80); + uint256 amount = inputs.getUint256(0xa0); (success, output) = protocol.call{value: value}(data); if (success) ERC1155(token).safeTransferFrom(address(this), map(recipient), id, amount, new bytes(0)); } @@ -418,9 +326,7 @@ abstract contract Dispatcher is Payments, V2SwapRouter, V3SwapRouter, Callbacks, /// @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) - } + value = inputs.getUint256(); data = inputs.toBytes(1); } } diff --git a/contracts/libraries/Calldata.sol b/contracts/libraries/Calldata.sol new file mode 100644 index 00000000..6a5d4c6f --- /dev/null +++ b/contracts/libraries/Calldata.sol @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.17; + +/// @title Calldata +/// @notice Calldata decoder used to extract arguments +library Calldata { + function getAddress(bytes calldata self) internal pure returns (address output) { + assembly { + output := calldataload(self.offset) + } + } + + function getUint256(bytes calldata self) internal pure returns (uint256 output) { + assembly { + output := calldataload(self.offset) + } + } + + function getUint160(bytes calldata self) internal pure returns (uint160 output) { + assembly { + output := calldataload(self.offset) + } + } + + function getBool(bytes calldata self) internal pure returns (bool output) { + assembly { + output := calldataload(self.offset) + } + } + + // with offset + function getAddress(bytes calldata self, uint8 offset) internal pure returns (address output) { + assembly { + output := calldataload(add(self.offset, offset)) + } + } + + function getUint256(bytes calldata self, uint8 offset) internal pure returns (uint256 output) { + assembly { + output := calldataload(add(self.offset, offset)) + } + } + + function getUint160(bytes calldata self, uint8 offset) internal pure returns (uint160 output) { + assembly { + output := calldataload(add(self.offset, offset)) + } + } + + function getUint8(bytes calldata self, uint8 offset) internal pure returns (uint8 output) { + assembly { + output := calldataload(add(self.offset, offset)) + } + } + + function getBool(bytes calldata self, uint8 offset) internal pure returns (bool output) { + assembly { + output := calldataload(add(self.offset, offset)) + } + } +}