diff --git a/contracts/base/Multicall.sol b/contracts/base/Multicall.sol new file mode 100644 index 00000000..bd926766 --- /dev/null +++ b/contracts/base/Multicall.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.19; + +import {IMulticall} from "../interfaces/IMulticall.sol"; + +/// @title Multicall +/// @notice Enables calling multiple methods in a single call to the contract +abstract contract Multicall is IMulticall { + /// @inheritdoc IMulticall + function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) { + results = new bytes[](data.length); + for (uint256 i = 0; i < data.length; i++) { + (bool success, bytes memory result) = address(this).delegatecall(data[i]); + + if (!success) { + // handle custom errors + if (result.length == 4) { + assembly { + revert(add(result, 0x20), mload(result)) + } + } + // Next 5 lines from https://ethereum.stackexchange.com/a/83577 + if (result.length < 68) revert(); + assembly { + result := add(result, 0x04) + } + revert(abi.decode(result, (string))); + } + + results[i] = result; + } + } +} diff --git a/contracts/base/PeripheryPayments.sol b/contracts/base/PeripheryPayments.sol new file mode 100644 index 00000000..f272da34 --- /dev/null +++ b/contracts/base/PeripheryPayments.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ERC20} from "solmate/tokens/ERC20.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/contracts/types/Currency.sol"; +import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; +import {IPeripheryPayments} from "../interfaces/IPeripheryPayments.sol"; + +abstract contract PeripheryPayments is IPeripheryPayments { + using CurrencyLibrary for Currency; + using SafeTransferLib for address; + using SafeTransferLib for ERC20; + + error InsufficientToken(); + error NativeTokenTransferFrom(); + + /// @inheritdoc IPeripheryPayments + function sweepToken(Currency currency, uint256 amountMinimum, address recipient) public payable override { + uint256 balanceCurrency = currency.balanceOfSelf(); + if (balanceCurrency < amountMinimum) revert InsufficientToken(); + + if (balanceCurrency > 0) { + currency.transfer(recipient, balanceCurrency); + } + } + + /// @param currency The currency to pay + /// @param payer The entity that must pay + /// @param recipient The entity that will receive payment + /// @param value The amount to pay + function pay(Currency currency, address payer, address recipient, uint256 value) internal { + if (payer == address(this)) { + // pay with tokens already in the contract (for the exact input multihop case) + currency.transfer(recipient, value); + } else { + if (currency.isNative()) revert NativeTokenTransferFrom(); + // pull payment + ERC20(Currency.unwrap(currency)).safeTransferFrom(payer, recipient, value); + } + } +} diff --git a/contracts/base/PeripheryValidation.sol b/contracts/base/PeripheryValidation.sol new file mode 100644 index 00000000..b8ea81d4 --- /dev/null +++ b/contracts/base/PeripheryValidation.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +abstract contract PeripheryValidation { + error TransactionTooOld(); + + modifier checkDeadline(uint256 deadline) { + if (block.timestamp > deadline) revert TransactionTooOld(); + _; + } +} diff --git a/contracts/base/SelfPermit.sol b/contracts/base/SelfPermit.sol new file mode 100644 index 00000000..40449636 --- /dev/null +++ b/contracts/base/SelfPermit.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; + +import {IERC20PermitAllowed} from "../interfaces/external/IERC20PermitAllowed.sol"; +import {ISelfPermit} from "../interfaces/ISelfPermit.sol"; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +/// @dev These functions are expected to be embedded in multicalls to allow EOAs to approve a contract and call a function +/// that requires an approval in a single transaction. +abstract contract SelfPermit is ISelfPermit { + /// @inheritdoc ISelfPermit + function selfPermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + public + payable + override + { + IERC20Permit(token).permit(msg.sender, address(this), value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitIfNecessary(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable + override + { + if (IERC20(token).allowance(msg.sender, address(this)) < value) selfPermit(token, value, deadline, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + public + payable + override + { + IERC20PermitAllowed(token).permit(msg.sender, address(this), nonce, expiry, true, v, r, s); + } + + /// @inheritdoc ISelfPermit + function selfPermitAllowedIfNecessary(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable + override + { + if (IERC20(token).allowance(msg.sender, address(this)) < type(uint256).max) { + selfPermitAllowed(token, nonce, expiry, v, r, s); + } + } +} diff --git a/contracts/interfaces/IMulticall.sol b/contracts/interfaces/IMulticall.sol new file mode 100644 index 00000000..dfa9db24 --- /dev/null +++ b/contracts/interfaces/IMulticall.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +/// @title Multicall interface +/// @notice Enables calling multiple methods in a single call to the contract +interface IMulticall { + /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed + /// @dev The `msg.value` should not be trusted for any method callable from multicall. + /// @param data The encoded function data for each of the calls to make to this contract + /// @return results The results from each of the calls passed in via data + function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); +} diff --git a/contracts/interfaces/IPeripheryPayments.sol b/contracts/interfaces/IPeripheryPayments.sol new file mode 100644 index 00000000..765b980f --- /dev/null +++ b/contracts/interfaces/IPeripheryPayments.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {Currency} from "@uniswap/v4-core/contracts/types/Currency.sol"; + +/// @title Periphery Payments +/// @notice Functions to ease deposits and withdrawals of ETH +interface IPeripheryPayments { + // TODO: figure out if we still need unwrapWETH9 from v3? + + /// @notice Transfers the full amount of a token held by this contract to recipient + /// @dev The amountMinimum parameter prevents malicious contracts from stealing the token from users + /// @param currency The contract address of the token which will be transferred to `recipient` + /// @param amountMinimum The minimum amount of token required for a transfer + /// @param recipient The destination address of the token + function sweepToken(Currency currency, uint256 amountMinimum, address recipient) external payable; +} diff --git a/contracts/interfaces/ISelfPermit.sol b/contracts/interfaces/ISelfPermit.sol new file mode 100644 index 00000000..cb2445f5 --- /dev/null +++ b/contracts/interfaces/ISelfPermit.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Self Permit +/// @notice Functionality to call permit on any EIP-2612-compliant token for use in the route +interface ISelfPermit { + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermit(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend a given token from `msg.sender` + /// @dev The `owner` is always msg.sender and the `spender` is always address(this). + /// Can be used instead of #selfPermit to prevent calls from failing due to a frontrun of a call to #selfPermit + /// @param token The address of the token spent + /// @param value The amount that can be spent of token + /// @param deadline A timestamp, the current blocktime must be less than or equal to this timestamp + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitIfNecessary(address token, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowed(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable; + + /// @notice Permits this contract to spend the sender's tokens for permit signatures that have the `allowed` parameter + /// @dev The `owner` is always msg.sender and the `spender` is always address(this) + /// Can be used instead of #selfPermitAllowed to prevent calls from failing due to a frontrun of a call to #selfPermitAllowed. + /// @param token The address of the token spent + /// @param nonce The current nonce of the owner + /// @param expiry The timestamp at which the permit is no longer valid + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function selfPermitAllowedIfNecessary(address token, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s) + external + payable; +} diff --git a/contracts/interfaces/external/IERC1271.sol b/contracts/interfaces/external/IERC1271.sol new file mode 100644 index 00000000..dcb30cb8 --- /dev/null +++ b/contracts/interfaces/external/IERC1271.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Interface for verifying contract-based account signatures +/// @notice Interface that verifies provided signature for the data +/// @dev Interface defined by EIP-1271 +interface IERC1271 { + /// @notice Returns whether the provided signature is valid for the provided data + /// @dev MUST return the bytes4 magic value 0x1626ba7e when function passes. + /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5). + /// MUST allow external calls. + /// @param hash Hash of the data to be signed + /// @param signature Signature byte array associated with _data + /// @return magicValue The bytes4 magic value 0x1626ba7e + function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue); +} diff --git a/contracts/interfaces/external/IERC20PermitAllowed.sol b/contracts/interfaces/external/IERC20PermitAllowed.sol new file mode 100644 index 00000000..7f2cf657 --- /dev/null +++ b/contracts/interfaces/external/IERC20PermitAllowed.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.5.0; + +/// @title Interface for permit +/// @notice Interface used by DAI/CHAI for permit +interface IERC20PermitAllowed { + /// @notice Approve the spender to spend some tokens via the holder signature + /// @dev This is the permit interface used by DAI and CHAI + /// @param holder The address of the token holder, the token owner + /// @param spender The address of the token spender + /// @param nonce The holder's nonce, increases at each call to permit + /// @param expiry The timestamp at which the permit is no longer valid + /// @param allowed Boolean that sets approval amount, true for type(uint256).max and false for 0 + /// @param v Must produce valid secp256k1 signature from the holder along with `r` and `s` + /// @param r Must produce valid secp256k1 signature from the holder along with `v` and `s` + /// @param s Must produce valid secp256k1 signature from the holder along with `r` and `v` + function permit( + address holder, + address spender, + uint256 nonce, + uint256 expiry, + bool allowed, + uint8 v, + bytes32 r, + bytes32 s + ) external; +}