- In RestrictTransferFrom: - Compare tokenIn with tokenIn from storage - Correct docstrings - Recompute storage slots with new names - Rename transferFromNeeded to isTransferFromAllowed - Don't track amount spent but subtract from amount allowed - In TychoRouter: Rename transferFromNeeded to isTransferFromAllowed Took 32 minutes
138 lines
5.2 KiB
Solidity
138 lines
5.2 KiB
Solidity
// SPDX-License-Identifier: BUSL-1.1
|
|
pragma solidity ^0.8.26;
|
|
|
|
import "@interfaces/IExecutor.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
|
|
import "@openzeppelin/contracts/utils/Address.sol";
|
|
|
|
error RestrictTransferFrom__AddressZero();
|
|
error RestrictTransferFrom__ExceededTransferFromAllowance(
|
|
uint256 allowedAmount, uint256 amountAttempted
|
|
);
|
|
error RestrictTransferFrom__DifferentTokenIn(
|
|
address tokenIn, address tokenInStorage
|
|
);
|
|
error RestrictTransferFrom__UnknownTransferType();
|
|
|
|
/**
|
|
* @title RestrictTransferFrom - Restrict transferFrom upto allowed amount of token
|
|
* @dev Restricts `transferFrom` (using `permit2` or regular `transferFrom`) upto
|
|
* allowed amount of token in per swap, while ensuring that the `transferFrom` is
|
|
* only performed on the input token upto input amount, from the msg.sender's wallet
|
|
* that calls the main swap method. Reverts if `transferFrom`s are attempted above
|
|
* this allowed amount.
|
|
*/
|
|
contract RestrictTransferFrom {
|
|
using SafeERC20 for IERC20;
|
|
|
|
IAllowanceTransfer public immutable permit2;
|
|
// keccak256("RestrictTransferFrom#TOKEN_IN_SLOT")
|
|
uint256 private constant _TOKEN_IN_SLOT =
|
|
0x25712b2458c26c244401cacab2c4d40a337e6c15af51d98c87ca8c05ed74935f;
|
|
// keccak256("RestrictTransferFrom#AMOUNT_ALLOWED_SLOT")
|
|
uint256 private constant _AMOUNT_ALLOWED_SLOT =
|
|
0x9042309497172c3d7a894cb22c754029d2b44522a8039afc41f7d5ad87a35cb5;
|
|
// keccak256("RestrictTransferFrom#IS_PERMIT2_SLOT")
|
|
uint256 private constant _IS_PERMIT2_SLOT =
|
|
0x8b09772a37ddaa0009affae61f4c227f5ae294cb166289f28313bcce05ea5358;
|
|
// keccak256("RestrictTransferFrom#SENDER_SLOT")
|
|
uint256 private constant _SENDER_SLOT =
|
|
0x6249046ac25ba4612871a1715b1abd1de7cf9c973c5045a9b08ce3f441ce6e3a;
|
|
|
|
constructor(address _permit2) {
|
|
if (_permit2 == address(0)) {
|
|
revert RestrictTransferFrom__AddressZero();
|
|
}
|
|
permit2 = IAllowanceTransfer(_permit2);
|
|
}
|
|
|
|
enum TransferType {
|
|
TransferFrom,
|
|
Transfer,
|
|
None
|
|
}
|
|
|
|
/**
|
|
* @dev This function is used to store the transfer information in the
|
|
* contract's storage. This is done as the first step in the swap process in TychoRouter.
|
|
*/
|
|
// slither-disable-next-line assembly
|
|
function _tstoreTransferFromInfo(
|
|
address tokenIn,
|
|
uint256 amountIn,
|
|
bool isPermit2,
|
|
bool isTransferFromAllowed
|
|
) internal {
|
|
uint256 amountAllowed = amountIn;
|
|
if (!isTransferFromAllowed) {
|
|
amountAllowed = 0;
|
|
}
|
|
assembly {
|
|
tstore(_TOKEN_IN_SLOT, tokenIn)
|
|
tstore(_AMOUNT_ALLOWED_SLOT, amountAllowed)
|
|
tstore(_IS_PERMIT2_SLOT, isPermit2)
|
|
tstore(_SENDER_SLOT, caller())
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev This function is used to transfer the tokens from the sender to the receiver.
|
|
* This function is called within the Executor contracts.
|
|
* If the TransferType is TransferFrom, it will check if the amount is within the allowed amount and transfer those funds from the user.
|
|
* If the TransferType is Transfer, it will transfer the funds from the TychoRouter to the receiver.
|
|
* If the TransferType is None, it will do nothing.
|
|
*/
|
|
// slither-disable-next-line assembly
|
|
function _transfer(
|
|
address receiver,
|
|
TransferType transferType,
|
|
address tokenIn,
|
|
uint256 amount
|
|
) internal {
|
|
if (transferType == TransferType.TransferFrom) {
|
|
address tokenInStorage;
|
|
bool isPermit2;
|
|
address sender;
|
|
uint256 amountAllowed;
|
|
assembly {
|
|
tokenInStorage := tload(_TOKEN_IN_SLOT)
|
|
amountAllowed := tload(_AMOUNT_ALLOWED_SLOT)
|
|
isPermit2 := tload(_IS_PERMIT2_SLOT)
|
|
sender := tload(_SENDER_SLOT)
|
|
}
|
|
if (amount > amountAllowed) {
|
|
revert RestrictTransferFrom__ExceededTransferFromAllowance(
|
|
amountAllowed, amount
|
|
);
|
|
}
|
|
if (tokenIn != tokenInStorage) {
|
|
revert RestrictTransferFrom__DifferentTokenIn(
|
|
tokenIn, tokenInStorage
|
|
);
|
|
}
|
|
amountAllowed -= amount;
|
|
assembly {
|
|
tstore(_AMOUNT_ALLOWED_SLOT, amountAllowed)
|
|
}
|
|
if (isPermit2) {
|
|
// Permit2.permit is already called from the TychoRouter
|
|
permit2.transferFrom(sender, receiver, uint160(amount), tokenIn);
|
|
} else {
|
|
// slither-disable-next-line arbitrary-send-erc20
|
|
IERC20(tokenIn).safeTransferFrom(sender, receiver, amount);
|
|
}
|
|
} else if (transferType == TransferType.Transfer) {
|
|
if (tokenIn == address(0)) {
|
|
Address.sendValue(payable(receiver), amount);
|
|
} else {
|
|
IERC20(tokenIn).safeTransfer(receiver, amount);
|
|
}
|
|
} else if (transferType == TransferType.None) {
|
|
return;
|
|
} else {
|
|
revert RestrictTransferFrom__UnknownTransferType();
|
|
}
|
|
}
|
|
}
|