Address Contract Verified
Address
0xEEe3fdCc5b9D7821570294b26070B2f45cFd8aEc
Balance
0 ETH
Nonce
1
Code Size
5808 bytes
Creator
0x0CEfd940...c936 at tx 0x5c304a4b...308002
Indexed Transactions
0
Contract Bytecode
5808 bytes
0x60806040526004361015610049575b3615610018575f80fd5b32331461002157005b7f1b10b0f9000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f5f3560e01c80633c376bec1461096557806346904840146109135780635c975abb146108d357806369fe0e2d146107ff578063715018a6146107605780638da5cb5b1461070c578063ac77ec2a146102b5578063ddca3f4314610279578063e74b981b146101d25763f2fde38b146100c2575061000e565b346101cf5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf576100f9610f31565b61010161162b565b73ffffffffffffffffffffffffffffffffffffffff81169081156101a35773ffffffffffffffffffffffffffffffffffffffff9074ffffffffffffffffffffffffffffffffffffffff0084549160081b167fffffffffffffffffffffff0000000000000000000000000000000000000000ff821617845560081c167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b6024837f1e4fbdf700000000000000000000000000000000000000000000000000000000815280600452fd5b80fd5b50346101cf5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5773ffffffffffffffffffffffffffffffffffffffff61021f610f31565b61022761162b565b16807fffffffffffffffffffffffff000000000000000000000000000000000000000060025416176002557f7a7b5a0a132f9e0581eb8527f66eae9ee89c2a3e79d4ac7e41a1f1f4d48a7fc28280a280f35b50346101cf57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf576020600154604051908152f35b506101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf576102e9610f31565b6101007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360112610708576101243567ffffffffffffffff811161070457610335903690600401610f54565b9061033e6111ef565b4260e435106106dc5761034f610f82565b90610358610fa5565b9061036283611222565b946101043594600286161561069e5786156106985760a4355b341115610670575b60a435966127106103966001548a611148565b043491155f146105e657916020939173ffffffffffffffffffffffffffffffffffffffff610408856104028c61043f9f9a988f908e6103e49160048960025416951615159485923390611349565b8d866103f8866103f2611074565b946110ba565b9216903390611349565b8c6110ba565b946040519c8d96879586937fab5898e8000000000000000000000000000000000000000000000000000000008552600485016110f4565b0393165af19485156105d95781956105a0575b604096509384906001161561058a5761046b308561143e565b60018111610547575b5080866104a76104876104ad9483611148565b926104a28960c4359561049a8288611148565b111595611148565b61115b565b91611111565b73ffffffffffffffffffffffffffffffffffffffff6104ca611097565b1615159050610536576104df84335b836114e1565b73ffffffffffffffffffffffffffffffffffffffff80865192858452866020850152169216907f764f0dc063c06f32d89a3f3af80c0db4be8a090901f589a478b447e0a51f09f1863392a482519182526020820152f35b6104df84610542611097565b6104d9565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0194506104ad9061058461057c87836110ba565b9633876114e1565b90610474565b5061059b60c4358681811015611111565b6104ad565b94506020863d6020116105d1575b816105bb60209383610fc8565b810103126105cd576040955194610452565b5f80fd5b3d91506105ae565b50604051903d90823e3d90fd5b9050888080808473ffffffffffffffffffffffffffffffffffffffff600254165af1610610611192565b5015610648579161043f9893918373ffffffffffffffffffffffffffffffffffffffff610408610642602097346110ba565b92610402565b6004897f4033e4e3000000000000000000000000000000000000000000000000000000008152fd5b6004887f1841b4e1000000000000000000000000000000000000000000000000000000008152fd5b8761037b565b86156106d65760a4355b3414610383576004887f1841b4e1000000000000000000000000000000000000000000000000000000008152fd5b876106a8565b6004847f769d11e4000000000000000000000000000000000000000000000000000000008152fd5b8280fd5b5080fd5b50346101cf57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5773ffffffffffffffffffffffffffffffffffffffff6020915460081c16604051908152f35b50346101cf57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5761079761162b565b8073ffffffffffffffffffffffffffffffffffffffff81547fffffffffffffffffffffff0000000000000000000000000000000000000000ff8116835560081c167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b50346101cf5760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5760043561083a61162b565b6127108111610875576020817f8c4d35e54a3f2ef1134138fd8ea3daee6a3c89e10d2665996babdf70261e2c7692600155604051908152a180f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f496e76616c6964466565000000000000000000000000000000000000000000006044820152fd5b50346101cf57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf5760ff60209154166040519015158152f35b50346101cf57807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126101cf57602073ffffffffffffffffffffffffffffffffffffffff60025416604051908152f35b506102207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105cd57610999610f31565b906101007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc3601126105cd5760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffedc3601126105cd576101e43567ffffffffffffffff81116105cd57610a10903690600401610f54565b6102049391933567ffffffffffffffff81116105cd57610a34903690600401610f54565b92610a3d6111ef565b4260e43510610f095760a43593612710610a5960015487611148565b0496610a6b610a66610f82565b611222565b9485610ee15761010435956002871615610ea65715610ea057865b341115610e78575b6e22d473030f116ddee9f6b43ac78ba33b156105cd57604051917f2b67b5700000000000000000000000000000000000000000000000000000000083523360048401526101243573ffffffffffffffffffffffffffffffffffffffff81168091036105cd5760248401526101443573ffffffffffffffffffffffffffffffffffffffff81168091036105cd5760448401526101643565ffffffffffff81168091036105cd5760648401526101843565ffffffffffff81168091036105cd5760848401526101a4359173ffffffffffffffffffffffffffffffffffffffff83168093036105cd57610b9e849283925f9560a48501526101c43560c485015261010060e4850152610104840191611036565b0381836e22d473030f116ddee9f6b43ac78ba35af18015610e6d57610e4a575b509060209291610c3c88610bf9610c73999a610bd8610f82565b73ffffffffffffffffffffffffffffffffffffffff60025416903390611260565b610c36610c04610f82565b610c0c611074565b9073ffffffffffffffffffffffffffffffffffffffff610c2c858d6110ba565b9216903390611260565b876110ba565b92604051978894859384937fab5898e8000000000000000000000000000000000000000000000000000000008552600485016110f4565b039173ffffffffffffffffffffffffffffffffffffffff3491165af1928315610e3f578493610e08575b50604093829184919060011615610ded57610cc0610cb9610f82565b309061143e565b60018111610d9d575b50610ce084836104a7610487610d16979883611148565b610ce8610fa5565b9073ffffffffffffffffffffffffffffffffffffffff610d06611097565b1615159050610d8f5733906114e1565b73ffffffffffffffffffffffffffffffffffffffff610d33610f82565b1673ffffffffffffffffffffffffffffffffffffffff610d51610fa5565b169084518381528460208201527f764f0dc063c06f32d89a3f3af80c0db4be8a090901f589a478b447e0a51f09f1863392a482519182526020820152f35b610d97611097565b906114e1565b610d1693507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff610ce0910194610de5610dd687836110ba565b9633610de0610f82565b6114e1565b949350610cc9565b610d16929350610e0360c4358381811015611111565b610ce0565b9392506020843d602011610e37575b81610e2460209383610fc8565b810103126105cd57604093519293610c9d565b3d9150610e17565b6040513d86823e3d90fd5b610c73965090610e5e5f6020959493610fc8565b610c3c5f975050909192610bbe565b6040513d5f823e3d90fd5b7f1841b4e1000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f610a86565b15610edb57865b3414610a8e577f1841b4e1000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f610ead565b7f50e3c4c2000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f769d11e4000000000000000000000000000000000000000000000000000000005f5260045ffd5b6004359073ffffffffffffffffffffffffffffffffffffffff821682036105cd57565b9181601f840112156105cd5782359167ffffffffffffffff83116105cd57602083818601950101116105cd57565b60243573ffffffffffffffffffffffffffffffffffffffff811681036105cd5790565b60443573ffffffffffffffffffffffffffffffffffffffff811681036105cd5790565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761100957604052565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe093818652868601375f8582860101520116010190565b60643573ffffffffffffffffffffffffffffffffffffffff811681036105cd5790565b60843573ffffffffffffffffffffffffffffffffffffffff811681036105cd5790565b919082039182116110c757565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b93929160209161110c91604087526040870191611036565b930152565b1561111a575050565b7f064a4ec6000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b818102929181159184041417156110c757565b8115611165570490565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b3d156111ea573d9067ffffffffffffffff821161100957604051916111df60207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8401160184610fc8565b82523d5f602084013e565b606090565b60ff5f54166111fa57565b7fd93c0665000000000000000000000000000000000000000000000000000000005f5260045ffd5b73ffffffffffffffffffffffffffffffffffffffff168015908115611245575090565b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee91501490565b92919073ffffffffffffffffffffffffffffffffffffffff8311611321575f938493608493604051937f36c785160000000000000000000000000000000000000000000000000000000085526004850152602484015260448301526064820152826e22d473030f116ddee9f6b43ac78ba35af180611308575b156112e057565b7ff4059071000000000000000000000000000000000000000000000000000000005f5260045ffd5b506e22d473030f116ddee9f6b43ac78ba33b15156112d9565b7f8112e119000000000000000000000000000000000000000000000000000000005f5260045ffd5b93156113cb5773ffffffffffffffffffffffffffffffffffffffff8311611321575f938493608493604051937f36c785160000000000000000000000000000000000000000000000000000000085526004850152602484015260448301526064820152826e22d473030f116ddee9f6b43ac78ba35af18061130857156112e057565b6064906020935f93604051927f23b872dd00000000000000000000000000000000000000000000000000000000845260048401526024830152604482015282855af1908161141c575b50156112e057565b90503d15611436575060015f5114601f3d11165b5f611414565b3b1515611430565b61144781611222565b1561145157503190565b9073ffffffffffffffffffffffffffffffffffffffff60246020928260405195869485937f70a08231000000000000000000000000000000000000000000000000000000008552166004840152165afa908115610e6d575f916114b2575090565b90506020813d6020116114d9575b816114cd60209383610fc8565b810103126105cd575190565b3d91506114c0565b826114eb57505050565b6114f481611222565b15611582575081471061155a575f80809373ffffffffffffffffffffffffffffffffffffffff829416611388f1611529611192565b5015611532575b565b7fb12d13eb000000000000000000000000000000000000000000000000000000005f5260045ffd5b7ff4d678b8000000000000000000000000000000000000000000000000000000005f5260045ffd5b9160446020925f9273ffffffffffffffffffffffffffffffffffffffff604051927fa9059cbb000000000000000000000000000000000000000000000000000000008452166004830152602482015282855af19081611609575b50611530577ffb7f5079000000000000000000000000000000000000000000000000000000005f5260045ffd5b90503d15611623575060015f5114601f3d11165b5f6115dc565b3b151561161d565b73ffffffffffffffffffffffffffffffffffffffff5f5460081c16330361164e57565b7f118cdaa7000000000000000000000000000000000000000000000000000000005f523360045260245ffdfea2646970667358221220c7a17ae34b3261597992a251f30d8c953f02626eefe458d471457bb1f365ca6264736f6c634300081d0033
Verified Source Code Full Match
Compiler: v0.8.29+commit.ab55807c
EVM: cancun
Optimization: Yes (100000 runs)
Aggregator.sol 188 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {EthReceiver} from "./utils/EthReceiver.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {UniERC20} from "./libraries/UniERC20.sol";
import {SafeERC20} from "./libraries/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IAggregationExecutor} from "./interfaces/IAggregationExecutor.sol";
import {IPermit2} from "./interfaces/IPermit2.sol";
import {IAggregator} from "./interfaces/IAggregator.sol";
/**
* @title Aggregator
* @notice Router that allows to use `IAggregationExecutor` for swaps.
*/
contract Aggregator is IAggregator, Pausable, EthReceiver, Ownable {
using UniERC20 for IERC20;
using SafeERC20 for IERC20;
uint256 private constant _PARTIAL_FILL = 1 << 0;
uint256 private constant _REQUIRES_EXTRA_ETH = 1 << 1;
uint256 private constant _USE_PERMIT2 = 1 << 2;
uint256 private constant FEE_DENOMINATOR = 10000;
// Fee in basis points (e.g., 100 = 1%)
uint256 public fee;
// Uniswap Permit2 address
address private constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
address public feeRecipient;
/// @notice Emitted when fee is updated
event FeeUpdated(uint256 newFeeBps);
constructor(address _owner, address _feeRecipient, uint256 _fee) Ownable(_owner) {
feeRecipient = _feeRecipient;
// Set initial fee in basis points
require(_fee <= FEE_DENOMINATOR, "InvalidFee");
fee = _fee;
}
// @inheritdoc IAggregator
function swap(IAggregationExecutor executor, SwapDescription calldata desc, bytes calldata data)
external
payable
whenNotPaused
returns (uint256 returnAmount, uint256 spentAmount)
{
require(desc.deadline >= block.timestamp, InvalidDeadline());
IERC20 srcToken = desc.srcToken;
IERC20 dstToken = desc.dstToken;
bool srcETH = srcToken.isETH();
if (desc.flags & _REQUIRES_EXTRA_ETH != 0) {
require(msg.value > (srcETH ? desc.amount : 0), InvalidMsgValue());
} else {
require(msg.value == (srcETH ? desc.amount : 0), InvalidMsgValue());
}
uint256 protocolFee = _getFee(desc.amount);
uint256 sendValue = msg.value;
if (!srcETH) {
srcToken.safeTransferFromUniversal(
msg.sender, address(feeRecipient), protocolFee, desc.flags & _USE_PERMIT2 != 0
);
srcToken.safeTransferFromUniversal(
msg.sender, desc.srcReceiver, desc.amount - protocolFee, desc.flags & _USE_PERMIT2 != 0
);
} else {
(bool success,) = feeRecipient.call{value: protocolFee}("");
require(success, FeeTransferFailed());
sendValue = msg.value - protocolFee;
}
returnAmount = IAggregationExecutor(executor).execute{value: sendValue}(data, desc.amount - protocolFee);
spentAmount = desc.amount;
if (desc.flags & _PARTIAL_FILL != 0) {
uint256 unspentAmount = srcToken.uniBalanceOf(address(this));
if (unspentAmount > 1) {
// we leave 1 wei on the router for gas optimisations reasons
unchecked {
unspentAmount--;
}
spentAmount -= unspentAmount;
srcToken.uniTransfer(payable(msg.sender), unspentAmount);
}
require(
returnAmount * desc.amount >= desc.minReturnAmount * spentAmount,
ReturnAmountIsNotEnough(returnAmount, desc.minReturnAmount * spentAmount / desc.amount)
);
} else {
require(returnAmount >= desc.minReturnAmount, ReturnAmountIsNotEnough(returnAmount, desc.minReturnAmount));
}
address payable dstReceiver = (desc.dstReceiver == address(0)) ? payable(msg.sender) : desc.dstReceiver;
dstToken.uniTransfer(dstReceiver, returnAmount);
// Emit swap event
emit SwapExecuted(msg.sender, address(srcToken), address(dstToken), spentAmount, returnAmount);
}
// @inheritdoc IAggregator
function swapWithPermit2(
IAggregationExecutor executor,
SwapDescription calldata desc,
IPermit2.PermitSingle calldata permit,
bytes calldata signature,
bytes calldata data
) external payable whenNotPaused returns (uint256 returnAmount, uint256 spentAmount) {
require(desc.deadline >= block.timestamp, InvalidDeadline());
uint256 protocolFee = _getFee(desc.amount);
uint256 sendValue = msg.value;
{
bool srcETH = desc.srcToken.isETH();
require(!srcETH, SrcTokenIsETH());
if (desc.flags & _REQUIRES_EXTRA_ETH != 0) {
require(msg.value > (srcETH ? desc.amount : 0), InvalidMsgValue());
} else {
require(msg.value == (srcETH ? desc.amount : 0), InvalidMsgValue());
}
IPermit2(_PERMIT2).permit(msg.sender, permit, signature);
desc.srcToken.safeTransferFromUniversal(msg.sender, address(feeRecipient), protocolFee, true);
desc.srcToken.safeTransferFromUniversal(msg.sender, desc.srcReceiver, desc.amount - protocolFee, true);
}
returnAmount = IAggregationExecutor(executor).execute{value: sendValue}(data, desc.amount - protocolFee);
spentAmount = desc.amount;
if (desc.flags & _PARTIAL_FILL != 0) {
uint256 unspentAmount = desc.srcToken.uniBalanceOf(address(this));
if (unspentAmount > 1) {
// we leave 1 wei on the router for gas optimisations reasons
unchecked {
unspentAmount--;
}
spentAmount -= unspentAmount;
desc.srcToken.uniTransfer(payable(msg.sender), unspentAmount);
}
require(
returnAmount * desc.amount >= desc.minReturnAmount * spentAmount,
ReturnAmountIsNotEnough(returnAmount, desc.minReturnAmount * spentAmount / desc.amount)
);
} else {
require(returnAmount >= desc.minReturnAmount, ReturnAmountIsNotEnough(returnAmount, desc.minReturnAmount));
}
desc.dstToken.uniTransfer(
(desc.dstReceiver == address(0)) ? payable(msg.sender) : desc.dstReceiver, returnAmount
);
// Emit swap event
emit SwapExecuted(msg.sender, address(desc.srcToken), address(desc.dstToken), spentAmount, returnAmount);
}
// @inheritdoc IAggregator
function setFeeRecipient(address _feeRecipient) external onlyOwner {
feeRecipient = _feeRecipient;
emit FeeRecipientUpdated(_feeRecipient);
}
/**
* @notice Updates protocol fee in basis points
* @param _fee New fee value in basis points (max 10000)
*/
function setFee(uint256 _fee) external onlyOwner {
require(_fee <= FEE_DENOMINATOR, "InvalidFee");
fee = _fee;
emit FeeUpdated(_fee);
}
function _unpause() internal override onlyOwner {
super._unpause();
}
function _pause() internal override onlyOwner {
super._pause();
}
function _getFee(uint256 amount) internal view returns (uint256) {
return amount * fee / FEE_DENOMINATOR;
}
}
Pausable.sol 112 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
EthReceiver.sol 15 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
abstract contract EthReceiver {
error EthDepositRejected();
receive() external payable {
_receive();
}
function _receive() internal virtual {
// solhint-disable-next-line avoid-tx-origin
require(msg.sender != tx.origin, EthDepositRejected());
}
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/IERC20.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
UniERC20.sol 73 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "./SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
/// @title Library, which allows usage of ETH as ERC20 and ERC20 itself. Uses SafeERC20 library for ERC20 interface.
library UniERC20 {
using SafeERC20 for IERC20;
error InsufficientBalance();
error ApproveCalledOnETH();
error NotEnoughValue();
error FromIsNotSender();
error ToIsNotThis();
error ETHTransferFailed();
uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
IERC20 private constant _ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
IERC20 private constant _ZERO_ADDRESS = IERC20(address(0));
/// @dev Returns true if `token` is ETH.
function isETH(IERC20 token) internal pure returns (bool) {
return (token == _ZERO_ADDRESS || token == _ETH_ADDRESS);
}
/// @dev Returns `account` ERC20 `token` balance.
function uniBalanceOf(IERC20 token, address account) internal view returns (uint256) {
if (isETH(token)) {
return account.balance;
} else {
return token.balanceOf(account);
}
}
/// @dev `token` transfer `to` `amount`.
/// Note that this function does nothing in case of zero amount.
function uniTransfer(IERC20 token, address payable to, uint256 amount) internal {
if (amount > 0) {
if (isETH(token)) {
if (address(this).balance < amount) revert InsufficientBalance();
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = to.call{value: amount, gas: _RAW_CALL_GAS_LIMIT}("");
if (!success) revert ETHTransferFailed();
} else {
token.safeTransfer(to, amount);
}
}
}
/// @dev `token` transfer `from` `to` `amount`.
/// Note that this function does nothing in case of zero amount.
function uniTransferFrom(IERC20 token, address payable from, address to, uint256 amount) internal {
if (amount > 0) {
if (isETH(token)) {
if (msg.value < amount) revert NotEnoughValue();
if (from != msg.sender) revert FromIsNotSender();
if (to != address(this)) revert ToIsNotThis();
if (msg.value > amount) {
// Return remainder if exist
unchecked {
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = from.call{value: msg.value - amount, gas: _RAW_CALL_GAS_LIMIT}("");
if (!success) revert ETHTransferFailed();
}
}
} else {
token.safeTransferFrom(from, to, amount);
}
}
}
}
SafeERC20.sol 454 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IDaiLikePermit} from "../interfaces/DaiLikePermit.sol";
import {IPermit2} from "../interfaces/IPermit2.sol";
import {IWETH} from "../interfaces/IWETH.sol";
/**
* @title Implements efficient safe methods for ERC20 interface.
* @notice Compared to the standard ERC20, this implementation offers several enhancements:
* 1. more gas-efficient, providing significant savings in transaction costs.
* 2. support for different permit implementations
* 3. forceApprove functionality
* 4. support for WETH deposit and withdraw
*/
library SafeERC20 {
error SafeTransferFailed();
error SafeTransferFromFailed();
error ForceApproveFailed();
error SafeIncreaseAllowanceFailed();
error SafeDecreaseAllowanceFailed();
error SafePermitBadLength();
error Permit2TransferAmountTooHigh();
error PermitFailed();
// Uniswap Permit2 address
address private constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
bytes4 private constant _PERMIT_LENGTH_ERROR = 0x68275857; // SafePermitBadLength.selector
uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
/**
* @notice Fetches the balance of a specific ERC20 token held by an account.
* Consumes less gas then regular `ERC20.balanceOf`.
* @dev Note that the implementation does not perform dirty bits cleaning, so it is the
* responsibility of the caller to make sure that the higher 96 bits of the `account` parameter are clean.
* @param token The IERC20 token contract for which the balance will be fetched.
* @param account The address of the account whose token balance will be fetched.
* @return tokenBalance The balance of the specified ERC20 token held by the account.
*/
function safeBalanceOf(IERC20 token, address account) internal view returns (uint256 tokenBalance) {
bytes4 selector = IERC20.balanceOf.selector;
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
mstore(0x00, selector)
mstore(0x04, account)
let success := staticcall(gas(), token, 0x00, 0x24, 0x00, 0x20)
tokenBalance := mload(0)
if or(iszero(success), lt(returndatasize(), 0x20)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
/**
* @notice Attempts to safely transfer tokens from one address to another.
* @dev If permit2 is true, uses the Permit2 standard; otherwise uses the standard ERC20 transferFrom.
* Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `from` and `to` parameters are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param from The address from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param amount The amount of tokens to transfer.
* @param permit2 If true, uses the Permit2 standard for the transfer; otherwise uses the standard ERC20 transferFrom.
*/
function safeTransferFromUniversal(IERC20 token, address from, address to, uint256 amount, bool permit2) internal {
if (permit2) {
safeTransferFromPermit2(token, from, to, amount);
} else {
safeTransferFrom(token, from, to, amount);
}
}
/**
* @notice Attempts to safely transfer tokens from one address to another using the ERC20 standard.
* @dev Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `from` and `to` parameters are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param from The address from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param amount The amount of tokens to transfer.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 amount) internal {
bytes4 selector = token.transferFrom.selector;
bool success;
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
let data := mload(0x40)
mstore(data, selector)
mstore(add(data, 0x04), from)
mstore(add(data, 0x24), to)
mstore(add(data, 0x44), amount)
success := call(gas(), token, 0, data, 100, 0x0, 0x20)
if success {
switch returndatasize()
case 0 { success := gt(extcodesize(token), 0) }
default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) }
}
}
if (!success) revert SafeTransferFromFailed();
}
/**
* @notice Attempts to safely transfer tokens from one address to another using the Permit2 standard.
* @dev Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `from` and `to` parameters are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param from The address from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param amount The amount of tokens to transfer.
*/
function safeTransferFromPermit2(IERC20 token, address from, address to, uint256 amount) internal {
if (amount > type(uint160).max) revert Permit2TransferAmountTooHigh();
bytes4 selector = IPermit2.transferFrom.selector;
bool success;
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
let data := mload(0x40)
mstore(data, selector)
mstore(add(data, 0x04), from)
mstore(add(data, 0x24), to)
mstore(add(data, 0x44), amount)
mstore(add(data, 0x64), token)
success := call(gas(), _PERMIT2, 0, data, 0x84, 0x0, 0x0)
if success { success := gt(extcodesize(_PERMIT2), 0) }
}
if (!success) revert SafeTransferFromFailed();
}
/**
* @notice Attempts to safely transfer tokens to another address.
* @dev Either requires `true` in return data, or requires target to be smart-contract and empty return data.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `to` parameter are clean.
* @param token The IERC20 token contract from which the tokens will be transferred.
* @param to The address to which the tokens will be transferred.
* @param value The amount of tokens to transfer.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
if (!_makeCall(token, token.transfer.selector, to, value)) {
revert SafeTransferFailed();
}
}
/**
* @notice Attempts to approve a spender to spend a certain amount of tokens.
* @dev If `approve(from, to, amount)` fails, it tries to set the allowance to zero, and retries the `approve` call.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `spender` parameter are clean.
* @param token The IERC20 token contract on which the call will be made.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
if (!_makeCall(token, token.approve.selector, spender, value)) {
if (
!_makeCall(token, token.approve.selector, spender, 0)
|| !_makeCall(token, token.approve.selector, spender, value)
) {
revert ForceApproveFailed();
}
}
}
/**
* @notice Safely increases the allowance of a spender.
* @dev Increases with safe math check. Checks if the increased allowance will overflow, if yes, then it reverts the transaction.
* Then uses `forceApprove` to increase the allowance.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `spender` parameter are clean.
* @param token The IERC20 token contract on which the call will be made.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to increase the allowance by.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 allowance = token.allowance(address(this), spender);
if (value > type(uint256).max - allowance) revert SafeIncreaseAllowanceFailed();
forceApprove(token, spender, allowance + value);
}
/**
* @notice Safely decreases the allowance of a spender.
* @dev Decreases with safe math check. Checks if the decreased allowance will underflow, if yes, then it reverts the transaction.
* Then uses `forceApprove` to increase the allowance.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `spender` parameter are clean.
* @param token The IERC20 token contract on which the call will be made.
* @param spender The address which will spend the funds.
* @param value The amount of tokens to decrease the allowance by.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 allowance = token.allowance(address(this), spender);
if (value > allowance) revert SafeDecreaseAllowanceFailed();
forceApprove(token, spender, allowance - value);
}
/**
* @notice Attempts to execute the `permit` function on the provided token with the sender and contract as parameters.
* Permit type is determined automatically based on permit calldata (IERC20Permit, IDaiLikePermit, and IPermit2).
* @dev Wraps `tryPermit` function and forwards revert reason if permit fails.
* @param token The IERC20 token to execute the permit function on.
* @param permit The permit data to be used in the function call.
*/
function safePermit(IERC20 token, bytes calldata permit) internal {
require(!tryPermit(token, msg.sender, address(this), permit), PermitFailed());
}
/**
* @notice Attempts to execute the `permit` function on the provided token with custom owner and spender parameters.
* Permit type is determined automatically based on permit calldata (IERC20Permit, IDaiLikePermit, and IPermit2).
* @dev Wraps `tryPermit` function and forwards revert reason if permit fails.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `owner` and `spender` parameters are clean.
* @param token The IERC20 token to execute the permit function on.
* @param owner The owner of the tokens for which the permit is made.
* @param spender The spender allowed to spend the tokens by the permit.
* @param permit The permit data to be used in the function call.
*/
function safePermit(IERC20 token, address owner, address spender, bytes calldata permit) internal {
require(!tryPermit(token, owner, spender, permit), PermitFailed());
}
/**
* @notice Attempts to execute the `permit` function on the provided token with the sender and contract as parameters.
* @dev Invokes `tryPermit` with sender as owner and contract as spender.
* @param token The IERC20 token to execute the permit function on.
* @param permit The permit data to be used in the function call.
* @return success Returns true if the permit function was successfully executed, false otherwise.
*/
function tryPermit(IERC20 token, bytes calldata permit) internal returns (bool success) {
return tryPermit(token, msg.sender, address(this), permit);
}
/**
* @notice The function attempts to call the permit function on a given ERC20 token.
* @dev The function is designed to support a variety of permit functions, namely: IERC20Permit, IDaiLikePermit, and IPermit2.
* It accommodates both Compact and Full formats of these permit types.
* Please note, it is expected that the `expiration` parameter for the compact Permit2 and the `deadline` parameter
* for the compact Permit are to be incremented by one before invoking this function. This approach is motivated by
* gas efficiency considerations; as the unlimited expiration period is likely to be the most common scenario, and
* zeros are cheaper to pass in terms of gas cost. Thus, callers should increment the expiration or deadline by one
* before invocation for optimized performance.
* Note that the implementation does not perform dirty bits cleaning, so it is the responsibility of
* the caller to make sure that the higher 96 bits of the `owner` and `spender` parameters are clean.
* @param token The address of the ERC20 token on which to call the permit function.
* @param owner The owner of the tokens. This address should have signed the off-chain permit.
* @param spender The address which will be approved for transfer of tokens.
* @param permit The off-chain permit data, containing different fields depending on the type of permit function.
* @return success A boolean indicating whether the permit call was successful.
*/
function tryPermit(IERC20 token, address owner, address spender, bytes calldata permit)
internal
returns (bool success)
{
// load function selectors for different permit standards
bytes4 permitSelector = IERC20Permit.permit.selector;
bytes4 daiPermitSelector = IDaiLikePermit.permit.selector;
bytes4 permit2Selector = IPermit2.permit.selector;
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
let ptr := mload(0x40)
// Switch case for different permit lengths, indicating different permit standards
switch permit.length
// Compact IERC20Permit
case 100 {
mstore(ptr, permitSelector) // store selector
mstore(add(ptr, 0x04), owner) // store owner
mstore(add(ptr, 0x24), spender) // store spender
// Compact IERC20Permit.permit(uint256 value, uint32 deadline, uint256 r, uint256 vs)
{
// stack too deep
let deadline := shr(224, calldataload(add(permit.offset, 0x20))) // loads permit.offset 0x20..0x23
let vs := calldataload(add(permit.offset, 0x44)) // loads permit.offset 0x44..0x63
calldatacopy(add(ptr, 0x44), permit.offset, 0x20) // store value = copy permit.offset 0x00..0x19
mstore(add(ptr, 0x64), sub(deadline, 1)) // store deadline = deadline - 1
mstore(add(ptr, 0x84), add(27, shr(255, vs))) // store v = most significant bit of vs + 27 (27 or 28)
calldatacopy(add(ptr, 0xa4), add(permit.offset, 0x24), 0x20) // store r = copy permit.offset 0x24..0x43
mstore(add(ptr, 0xc4), shr(1, shl(1, vs))) // store s = vs without most significant bit
}
// IERC20Permit.permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0xe4, 0, 0)
}
// Compact IDaiLikePermit
case 72 {
mstore(ptr, daiPermitSelector) // store selector
mstore(add(ptr, 0x04), owner) // store owner
mstore(add(ptr, 0x24), spender) // store spender
// Compact IDaiLikePermit.permit(uint32 nonce, uint32 expiry, uint256 r, uint256 vs)
{
// stack too deep
let expiry := shr(224, calldataload(add(permit.offset, 0x04))) // loads permit.offset 0x04..0x07
let vs := calldataload(add(permit.offset, 0x28)) // loads permit.offset 0x28..0x47
mstore(add(ptr, 0x44), shr(224, calldataload(permit.offset))) // store nonce = copy permit.offset 0x00..0x03
mstore(add(ptr, 0x64), sub(expiry, 1)) // store expiry = expiry - 1
mstore(add(ptr, 0x84), true) // store allowed = true
mstore(add(ptr, 0xa4), add(27, shr(255, vs))) // store v = most significant bit of vs + 27 (27 or 28)
calldatacopy(add(ptr, 0xc4), add(permit.offset, 0x08), 0x20) // store r = copy permit.offset 0x08..0x27
mstore(add(ptr, 0xe4), shr(1, shl(1, vs))) // store s = vs without most significant bit
}
// IDaiLikePermit.permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0x104, 0, 0)
}
// IERC20Permit
case 224 {
mstore(ptr, permitSelector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length) // copy permit calldata
// IERC20Permit.permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0xe4, 0, 0)
}
// IDaiLikePermit
case 256 {
mstore(ptr, daiPermitSelector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length) // copy permit calldata
// IDaiLikePermit.permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s)
success := call(gas(), token, 0, ptr, 0x104, 0, 0)
}
// Compact IPermit2
case 96 {
// Compact IPermit2.permit(uint160 amount, uint32 expiration, uint32 nonce, uint32 sigDeadline, uint256 r, uint256 vs)
mstore(ptr, permit2Selector) // store selector
mstore(add(ptr, 0x04), owner) // store owner
mstore(add(ptr, 0x24), token) // store token
calldatacopy(add(ptr, 0x50), permit.offset, 0x14) // store amount = copy permit.offset 0x00..0x13
// and(0xffffffffffff, ...) - conversion to uint48
mstore(add(ptr, 0x64), and(0xffffffffffff, sub(shr(224, calldataload(add(permit.offset, 0x14))), 1))) // store expiration = ((permit.offset 0x14..0x17 - 1) & 0xffffffffffff)
mstore(add(ptr, 0x84), shr(224, calldataload(add(permit.offset, 0x18)))) // store nonce = copy permit.offset 0x18..0x1b
mstore(add(ptr, 0xa4), spender) // store spender
// and(0xffffffffffff, ...) - conversion to uint48
mstore(add(ptr, 0xc4), and(0xffffffffffff, sub(shr(224, calldataload(add(permit.offset, 0x1c))), 1))) // store sigDeadline = ((permit.offset 0x1c..0x1f - 1) & 0xffffffffffff)
mstore(add(ptr, 0xe4), 0x100) // store offset = 256
mstore(add(ptr, 0x104), 0x40) // store length = 64
calldatacopy(add(ptr, 0x124), add(permit.offset, 0x20), 0x20) // store r = copy permit.offset 0x20..0x3f
calldatacopy(add(ptr, 0x144), add(permit.offset, 0x40), 0x20) // store vs = copy permit.offset 0x40..0x5f
// IPermit2.permit(address owner, PermitSingle calldata permitSingle, bytes calldata signature)
success := call(gas(), _PERMIT2, 0, ptr, 0x164, 0, 0)
}
// IPermit2
case 352 {
mstore(ptr, permit2Selector)
calldatacopy(add(ptr, 0x04), permit.offset, permit.length) // copy permit calldata
// IPermit2.permit(address owner, PermitSingle calldata permitSingle, bytes calldata signature)
success := call(gas(), _PERMIT2, 0, ptr, 0x164, 0, 0)
}
// Unknown
default {
mstore(ptr, _PERMIT_LENGTH_ERROR)
revert(ptr, 4)
}
}
}
/**
* @dev Executes a low level call to a token contract, making it resistant to reversion and erroneous boolean returns.
* @param token The IERC20 token contract on which the call will be made.
* @param selector The function signature that is to be called on the token contract.
* @param to The address to which the token amount will be transferred.
* @param amount The token amount to be transferred.
* @return success A boolean indicating if the call was successful. Returns 'true' on success and 'false' on failure.
* In case of success but no returned data, validates that the contract code exists.
* In case of returned data, ensures that it's a boolean `true`.
*/
function _makeCall(IERC20 token, bytes4 selector, address to, uint256 amount) private returns (bool success) {
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
let data := mload(0x40)
mstore(data, selector)
mstore(add(data, 0x04), to)
mstore(add(data, 0x24), amount)
success := call(gas(), token, 0, data, 0x44, 0x0, 0x20)
if success {
switch returndatasize()
case 0 { success := gt(extcodesize(token), 0) }
default { success := and(gt(returndatasize(), 31), eq(mload(0), 1)) }
}
}
}
/**
* @notice Safely deposits a specified amount of Ether into the IWETH contract. Consumes less gas then regular `IWETH.deposit`.
* @param weth The IWETH token contract.
* @param amount The amount of Ether to deposit into the IWETH contract.
*/
function safeDeposit(IWETH weth, uint256 amount) internal {
if (amount > 0) {
bytes4 selector = IWETH.deposit.selector;
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
mstore(0, selector)
if iszero(call(gas(), weth, amount, 0, 4, 0, 0)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
}
/**
* @notice Safely withdraws a specified amount of wrapped Ether from the IWETH contract. Consumes less gas then regular `IWETH.withdraw`.
* @dev Uses inline assembly to interact with the IWETH contract.
* @param weth The IWETH token contract.
* @param amount The amount of wrapped Ether to withdraw from the IWETH contract.
*/
function safeWithdraw(IWETH weth, uint256 amount) internal {
bytes4 selector = IWETH.withdraw.selector;
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
mstore(0, selector)
mstore(4, amount)
if iszero(call(gas(), weth, 0, 0, 0x24, 0, 0)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
/**
* @notice Safely withdraws a specified amount of wrapped Ether from the IWETH contract to a specified recipient.
* Consumes less gas then regular `IWETH.withdraw`.
* @param weth The IWETH token contract.
* @param amount The amount of wrapped Ether to withdraw from the IWETH contract.
* @param to The recipient of the withdrawn Ether.
*/
function safeWithdrawTo(IWETH weth, uint256 amount, address to) internal {
safeWithdraw(weth, amount);
if (to != address(this)) {
assembly ("memory-safe") {
// solhint-disable-line no-inline-assembly
if iszero(call(_RAW_CALL_GAS_LIMIT, to, amount, 0, 0, 0, 0)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize())
revert(ptr, returndatasize())
}
}
}
}
}
Ownable.sol 100 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
IAggregationExecutor.sol 6 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
interface IAggregationExecutor {
function execute(bytes memory data, uint256 amountIn) external payable returns (uint256);
}
IPermit2.sol 41 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
interface IPermit2 {
struct PermitDetails {
// ERC20 token address
address token;
// the maximum amount allowed to spend
uint160 amount;
// timestamp at which a spender's token allowances become invalid
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice The permit message signed for a single token allownce
struct PermitSingle {
// the permit data for a single token alownce
PermitDetails details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice Packed allowance
struct PackedAllowance {
// amount allowed
uint160 amount;
// permission expiry
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
function transferFrom(address user, address spender, uint160 amount, address token) external;
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
function allowance(address user, address token, address spender) external view returns (PackedAllowance memory);
}
IAggregator.sol 84 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IAggregationExecutor} from "./IAggregationExecutor.sol";
import {IPermit2} from "./IPermit2.sol";
/**
* @title IAggregator
* @notice Interface for GenericRouter that allows to use `IAggregationExecutor` for swaps.
*/
interface IAggregator {
// Custom errors
error InvalidMsgValue();
error ReturnAmountIsNotEnough(uint256 returnAmount, uint256 minReturnAmount);
error InvalidDeadline();
error FeeTransferFailed();
error SrcTokenIsETH();
// Events
event FeeRecipientUpdated(address indexed newFeeRecipient);
event SwapExecuted(
address indexed user, address indexed srcToken, address indexed dstToken, uint256 srcAmount, uint256 dstAmount
);
// Structures
struct SwapDescription {
IERC20 srcToken;
IERC20 dstToken;
address payable srcReceiver;
address payable dstReceiver;
uint256 amount;
uint256 minReturnAmount;
uint256 deadline;
uint256 flags;
}
/**
* @notice Performs a swap, delegating all calls encoded in `data` to `executor`. See tests for usage examples.
* @dev Router keeps 1 wei of every token on the contract balance for gas optimisations reasons.
* This affects first swap of every token by leaving 1 wei on the contract.
* @param executor Aggregation executor that executes calls described in `data`.
* @param desc Swap description.
* @param data Encoded calls that `caller` should execute in between of swaps.
* @return returnAmount Resulting token amount.
* @return spentAmount Source token amount.
*/
function swap(IAggregationExecutor executor, SwapDescription calldata desc, bytes calldata data)
external
payable
returns (uint256 returnAmount, uint256 spentAmount);
/**
* @notice Performs a swap using Permit2 for token approval.
* @dev This function requires explicit permit2 structure and signature.
* @param executor Aggregation executor that executes calls described in `data`.
* @param desc Swap description for permit2.
* @param permit The permit2 data structure.
* @param signature The signature for the permit.
* @param data Encoded calls that `executor` should execute.
* @return returnAmount Resulting token amount.
* @return spentAmount Source token amount.
*/
function swapWithPermit2(
IAggregationExecutor executor,
SwapDescription calldata desc,
IPermit2.PermitSingle calldata permit,
bytes calldata signature,
bytes calldata data
) external payable returns (uint256 returnAmount, uint256 spentAmount);
/**
* @notice Sets the fee recipient address.
* @dev Only owner can call this function.
* @param _feeRecipient New fee recipient address.
*/
function setFeeRecipient(address _feeRecipient) external;
/**
* @notice Returns the current fee recipient address.
* @return The address of the current fee recipient.
*/
function feeRecipient() external view returns (address);
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
IERC20Metadata.sol 26 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity >=0.6.2;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC-20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
IERC20Permit.sol 90 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity >=0.4.16;
/**
* @dev Interface of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[ERC-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC-20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
DaiLikePermit.sol 15 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
interface IDaiLikePermit {
function permit(
address holder,
address spender,
uint256 nonce,
uint256 expiry,
bool allowed,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
IWETH.sol 14 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.29;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IWETH is IERC20 {
event Deposit(address indexed dst, uint256 wad);
event Withdrawal(address indexed src, uint256 wad);
function deposit() external payable;
function withdraw(uint256 amount) external;
}
Read Contract
fee 0xddca3f43 → uint256
feeRecipient 0x46904840 → address
owner 0x8da5cb5b → address
paused 0x5c975abb → bool
Write Contract 6 functions
These functions modify contract state and require a wallet transaction to execute.
renounceOwnership 0x715018a6
No parameters
setFee 0x69fe0e2d
uint256 _fee
setFeeRecipient 0xe74b981b
address _feeRecipient
swap 0x6b1ef56f
address executor
tuple desc
bytes data
returns: uint256, uint256
swapWithPermit2 0x982ee81b
address executor
tuple desc
tuple permit
bytes signature
bytes data
returns: uint256, uint256
transferOwnership 0xf2fde38b
address newOwner
Recent Transactions
No transactions found for this address