// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC4626.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../token/ERC20/IERC20.sol";
import {IERC20Metadata} from "../token/ERC20/extensions/IERC20Metadata.sol";
/**
* @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
* https://eips.ethereum.org/EIPS/eip-4626[ERC-4626].
*/
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/**
* @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
*
* - MUST be an ERC-20 token contract.
* - MUST NOT revert.
*/
function asset() external view returns (address assetTokenAddress);
/**
* @dev Returns the total amount of the underlying asset that is “managed” by Vault.
*
* - SHOULD include any compounding that occurs from yield.
* - MUST be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT revert.
*/
function totalAssets() external view returns (uint256 totalManagedAssets);
/**
* @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToShares(uint256 assets) external view returns (uint256 shares);
/**
* @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
* scenario where all the conditions are met.
*
* - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
* - MUST NOT show any variations depending on the caller.
* - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
* - MUST NOT revert.
*
* NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
* “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
* from.
*/
function convertToAssets(uint256 shares) external view returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
* through a deposit call.
*
* - MUST return a limited value if receiver is subject to some deposit limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
* - MUST NOT revert.
*/
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
* call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
* in the same transaction.
* - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
* deposit would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewDeposit(uint256 assets) external view returns (uint256 shares);
/**
* @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* deposit execution, and are accounted for during deposit.
* - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
* - MUST return a limited value if receiver is subject to some mint limit.
* - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
* - MUST NOT revert.
*/
function maxMint(address receiver) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
* current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
* in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
* same transaction.
* - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
* would be accepted, regardless if the user has enough tokens approved, etc.
* - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by minting.
*/
function previewMint(uint256 shares) external view returns (uint256 assets);
/**
* @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
*
* - MUST emit the Deposit event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
* execution, and are accounted for during mint.
* - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
* approving enough underlying tokens to the Vault contract, etc).
*
* NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
*/
function mint(uint256 shares, address receiver) external returns (uint256 assets);
/**
* @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
* Vault, through a withdraw call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
* call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
* called
* in the same transaction.
* - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
* the withdrawal would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by depositing.
*/
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
/**
* @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* withdraw execution, and are accounted for during withdraw.
* - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
/**
* @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
* through a redeem call.
*
* - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
* - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
* - MUST NOT revert.
*/
function maxRedeem(address owner) external view returns (uint256 maxShares);
/**
* @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
* given current on-chain conditions.
*
* - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
* in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
* same transaction.
* - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
* redemption would be accepted, regardless if the user has enough shares, etc.
* - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
* - MUST NOT revert.
*
* NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
* share price or some other type of condition, meaning the depositor will lose assets by redeeming.
*/
function previewRedeem(uint256 shares) external view returns (uint256 assets);
/**
* @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver.
*
* - MUST emit the Withdraw event.
* - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
* redeem execution, and are accounted for during redeem.
* - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
* not having enough shares, etc).
*
* NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
* Those methods should be performed separately.
*/
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.19;
import { StrategyData } from "../helpers/VaultTypes.sol";
import { IERC4626 } from "openzeppelin/interfaces/IERC4626.sol";
/**
* @notice IMaxApyVault contains the main interface for MaxApy V2 Vaults
*/
interface IMaxApyVault is IERC4626 {
function report(
uint128 unrealizedGain,
uint128 loss,
uint128 debtPayment,
address managementFeeReceiver
)
external
returns (uint256);
/// Ownership
function transferOwnership(address newOwner) external payable;
function renounceOwnership() external payable;
function requestOwnershipHandover() external payable;
function cancelOwnershipHandover() external payable;
function completeOwnershipHandover(address pendingOwner) external payable;
/// View ownership
function ownershipHandoverExpiresAt(address pendingOwner) external view returns (uint256);
function owner() external view returns (address result);
/// Roles
function grantRoles(address user, uint256 roles) external payable;
function revokeRoles(address user, uint256 roles) external payable;
function renounceRoles(uint256 roles) external payable;
/// View roles
function ADMIN_ROLE() external returns (uint256);
function EMERGENCY_ADMIN_ROLE() external returns (uint256);
function KEEPER_ROLE() external returns (uint256);
function STRATEGY_ROLE() external returns (uint256);
function hasAnyRole(address user, uint256 roles) external view returns (bool result);
function hasAllRoles(address user, uint256 roles) external view returns (bool result);
function rolesOf(address user) external view returns (uint256 roles);
function rolesFromOrdinals(uint8[] memory ordinals) external pure returns (uint256 roles);
function ordinalsFromRoles(uint256 roles) external pure returns (uint8[] memory ordinals);
/// Vault configuration
function debtRatio() external returns (uint256);
function totalDebt() external returns (uint256);
function totalIdle() external returns (uint256);
function strategies(address strategy) external returns (StrategyData memory);
function withdrawalQueue(uint256 index) external returns (address);
function emergencyShutdown() external returns (bool);
function nexHarvestStrategyIndex() external view returns (uint8);
function autoPilotEnabled() external returns (bool);
/// Vault management
function setEmergencyShutdown(bool _emergencyShutdown) external;
function addStrategy(
address newStrategy,
uint256 strategyDebtRatio,
uint256 strategyMaxDebtPerHarvest,
uint256 strategyMinDebtPerHarvest,
uint256 strategyPerformanceFee
)
external;
function revokeStrategy(address strategy) external;
function removeStrategy(address strategy) external;
function exitStrategy(address strategy) external;
function updateStrategyData(
address strategy,
uint256 newDebtRatio,
uint256 newMaxDebtPerHarvest,
uint256 newMinDebtPerHarvest,
uint256 newPerformanceFee
)
external;
function setWithdrawalQueue(address[20] calldata queue) external;
function setPerformanceFee(uint256 _performanceFee) external;
function setManagementFee(uint256 _managementFee) external;
function setDepositLimit(uint256 _depositLimit) external;
function setTreasury(address _treasury) external;
function setAutopilotEnabled(bool _autoPilotEnabled) external;
function setAutoPilot(bool _autoPilot) external;
/// Vault view functions
function performanceFee() external returns (uint256);
function managementFee() external returns (uint256);
function AUTOPILOT_HARVEST_INTERVAL() external returns (uint256);
function MAXIMUM_STRATEGIES() external returns (uint256);
function debtOutstanding(address strategy) external view returns (uint256);
function totalAssets() external view returns (uint256);
function totalDeposits() external view returns (uint256);
function lastReport() external view returns (uint256);
function treasury() external view returns (address);
function sharePrice() external view returns (uint256);
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.19;
interface IWrappedToken {
function deposit() external payable;
function transfer(address to, uint256 value) external returns (bool);
function withdraw(uint256) external;
function approve(address spender, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.19;
import { IWrappedToken } from "src/interfaces/IWrappedToken.sol";
import { IMaxApyVault } from "src/interfaces/IMaxApyVault.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";
import { IERC20Permit } from "openzeppelin/token/ERC20/extensions/IERC20Permit.sol";
/// @title MaxApy Vault Universal Router
/// @notice A helper contract to safely and easily interact with MaxApy universal vaults
/// @author Adapted from: https://github.com/ERC4626-Alliance/ERC4626-Contracts/blob/main/src/ERC4626Router.sol
contract MaxApyRouter {
using SafeTransferLib for address;
////////////////////////////////////////////////////////////////
/// CONSTANTS ///
////////////////////////////////////////////////////////////////
/// @notice The chain's wrapped native token
IWrappedToken public immutable wrappedToken;
////////////////////////////////////////////////////////////////
/// ERRORS ///
////////////////////////////////////////////////////////////////
error FailedNativeTransfer();
error InsufficientShares();
error InsufficientAssets();
error ReceiveNotAllowed();
error InvalidRecipient();
/// @notice Create the WrappedToken Gateway
/// @param _wrappedToken The wrapped token of the chain the contract will be deployed to
constructor(IWrappedToken _wrappedToken) {
wrappedToken = _wrappedToken;
}
/// @notice Deposits `amount` tokens in the vault, issuing shares to `recipient`
/// @param vault The MaxApy vault to interact with
/// @param amount The amount of underlying assets to deposit
/// @param recipient The address to issue the shares from MaxApy's Vault to
/// @param minSharesOut The minimum acceptable amount of vault shares to get after the deposit
/// @return sharesOut The actual amount of minted shares
function deposit(
IMaxApyVault vault,
uint256 amount,
address recipient,
uint256 minSharesOut
)
external
returns (uint256 sharesOut)
{
if (recipient == address(0)) revert InvalidRecipient();
address asset = vault.asset();
address cachedVault = address(vault);
asset.safeTransferFrom(msg.sender, address(this), amount);
_approveMax(cachedVault, asset);
assembly ("memory-safe") {
// Cache the free memory pointer
let m := mload(0x40)
// Store MaxApy vault's `deposit()` function selector:
// `bytes4(keccak256("deposit(uint256,address)"))`
mstore(0x00, 0x6e553f65)
mstore(0x20, amount) // Append the `amount` argument
mstore(0x40, recipient) // Append the `recipient` argument
// Deposit into MaxApy vault
if iszero(
call(
gas(), // Remaining amount of gas
cachedVault, // Address of `vault`
0, // `msg.value`
0x1c, // byte offset in memory where calldata starts
0x44, // size of the calldata to copy
0x00, // byte offset in memory to store the return data
0x20 // size of the return data
)
) {
// If call failed, throw the error thrown in the previous `call`
revert(0x00, 0x04)
}
// cache shares
sharesOut := mload(0x00)
// check that shares aren't fewer than requested
if lt(sharesOut, minSharesOut) {
// throw the `InsufficientShares` error
mstore(0x00, 0x39996567)
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer
}
}
/// @notice Deposits `amount` tokens in the vault, issuing shares to `recipient`
/// @param vault The MaxApy vault to interact with
/// @param amount The amount of underlying assets to deposit
/// @param recipient The address to issue the shares from MaxApy's Vault to
/// @param deadline Deadline for the EIP712-Permit signature
/// @param v `v` component of the digital signature
/// @param r `r` component of the digital signature
/// @param s `s` component of the digital signature
/// @param minSharesOut The minimum acceptable amount of vault shares to get after the deposit
/// @return sharesOut The actual amount of minted shares
function depositWithPermit(
IMaxApyVault vault,
uint256 amount,
address recipient,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s,
uint256 minSharesOut
)
external
returns (uint256 sharesOut)
{
if (recipient == address(0)) revert InvalidRecipient();
address asset = vault.asset();
address cachedVault = address(vault);
IERC20Permit(asset).permit(msg.sender, address(this), amount, deadline, v, r, s);
asset.safeTransferFrom(msg.sender, address(this), amount);
_approveMax(cachedVault, asset);
assembly ("memory-safe") {
// Cache the free memory pointer
let m := mload(0x40)
// Store MaxApy vault's `deposit()` function selector:
// `bytes4(keccak256("deposit(uint256,address)"))`
mstore(0x00, 0x6e553f65)
mstore(0x20, amount) // Append the `amount` argument
mstore(0x40, recipient) // Append the `recipient` argument
// Deposit into MaxApy vault
if iszero(
call(
gas(), // Remaining amount of gas
cachedVault, // Address of `vault`
0, // `msg.value`
0x1c, // byte offset in memory where calldata starts
0x44, // size of the calldata to copy
0x00, // byte offset in memory to store the return data
0x20 // size of the return data
)
) {
// If call failed, throw the error thrown in the previous `call`
revert(0x00, 0x04)
}
// cache shares
sharesOut := mload(0x00)
// check that shares aren't fewer than requested
if lt(sharesOut, minSharesOut) {
// throw the `InsufficientShares` error
mstore(0x00, 0x39996567)
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer
}
}
/// @notice Deposits `msg.value` of `_wrappedToken`, issuing shares to `recipient`
/// @param vault The MaxApy vault to interact with
/// @param recipient The address to issue the shares from MaxApy's Vault to
/// @param minSharesOut The minimum acceptable amount of vault shares to get after the deposit
/// @return sharesOut The actual amount of minted shares
function depositNative(
IMaxApyVault vault,
address recipient,
uint256 minSharesOut
)
external
payable
returns (uint256 sharesOut)
{
if (recipient == address(0)) revert InvalidRecipient();
// Cache `wrappedToken` and `vault` due to assembly's immutable access restrictions
address cachedWrappedToken = address(wrappedToken);
address cachedVault = address(vault);
_approveMax(cachedVault, cachedWrappedToken);
assembly ("memory-safe") {
// Check if `msg.value` is 0
if iszero(callvalue()) {
// Throw the `InvalidZeroValue()` error
mstore(0x00, 0xef7a63d0)
revert(0x1c, 0x04)
}
// Cache the free memory pointer
let m := mload(0x40)
// Store Wrapped Token's `deposit()` function selector:
// `bytes4(keccak256("deposit()"))`
mstore(0x00, 0xd0e30db0)
// Deposit native token in exchange for wrapped native token
// Note: using some wrapped tokens' fallback function for deposit allows saving the previous
// selector loading into memory to call wrappedToken's `deposit()`.
// This is avoided due to some chain's wrapped native versions not allowing such behaviour
if iszero(
call(
gas(), // Remaining amount of gas
cachedWrappedToken, // Address of `wrappedToken`
callvalue(), // `msg.value`
0x1c, // byte offset in memory where calldata starts
0x04, // size of the calldata to copy
0x00, // byte offset in memory to store the return data
0x00 // size of the return data
)
) {
// Throw the `WrappedTokenDepositFailed()` error
mstore(0x00, 0x22cd2378)
revert(0x1c, 0x04)
}
// Store MaxApy vault's `deposit()` function selector:
// `bytes4(keccak256("deposit(uint256,address)"))`
mstore(0x00, 0x6e553f65)
mstore(0x20, callvalue()) // Append the `amount` argument
mstore(0x40, recipient) // Append the `recipient` argument
// Deposit into MaxApy vault
if iszero(
call(
gas(), // Remaining amount of gas
cachedVault, // Address of `vault`
0, // `msg.value`
0x1c, // byte offset in memory where calldata starts
0x44, // size of the calldata to copy
0x00, // byte offset in memory to store the return data
0x20 // size of the return data
)
) {
// If call failed, throw the error thrown in the previous `call`
revert(0x00, 0x04)
}
// cache shares
sharesOut := mload(0x00)
// check that shares aren't fewer than requested
if lt(sharesOut, minSharesOut) {
// throw the `InsufficientShares` error
mstore(0x00, 0x39996567)
revert(0x1c, 0x04)
}
mstore(0x40, m) // Restore the free memory pointer
}
}
/// @notice Withdraws the calling account's tokens from MaxApy's Vault, redeeming
/// amount `shares` for the corresponding amount of tokens, which will be transferred to
/// `recipient`
/// @param vault The MaxApy vault to interact with
/// @param shares How many shares to try and redeem for tokens
/// @param recipient The address to issue the shares from MaxApy's Vault to
/// @param minAmountOut The minimum acceptable amount of assets to get in exchange for the burnt shares
/// @return amountOut The actual amount of redeemed assets
function redeem(
IMaxApyVault vault,
uint256 shares,
address recipient,
uint256 minAmountOut
)
external
returns (uint256 amountOut)
{
if (recipient == address(0)) revert InvalidRecipient();
// Cache `wrappedToken` and `vault` due to assembly's immutable access restrictions
address cachedVault = address(vault);
assembly ("memory-safe") {
// Cache the free memory pointer
let m := mload(0x40)
// Store `vault`'s `redeem()` function selector:
// `bytes4(keccak256("redeem(uint256,address,address)"))`
mstore(0x00, 0xba087652)
mstore(0x20, shares) // append the `shares` argument
mstore(0x40, recipient) // append the `recipient` argument
mstore(0x60, caller()) // append the `operator` argument
// Withdraw from MaxApy vault
if iszero(
call(
gas(), // Remaining amount of gas
cachedVault, // Address of `vault`
0, // `msg.value`
0x1c, // byte offset in memory where calldata starts
0x64, // size of the calldata to copy
0x00, // byte offset in memory to store the return data
0x20 // size of the return data
)
) {
// If call failed, throw the error thrown in the previous `call`
revert(0x00, 0x04)
}
// Store `amountOut` returned by the previous call to `withdraw()`
amountOut := mload(0x00)
if lt(amountOut, minAmountOut) {
// Throw the `InsufficientAssets` error
mstore(0x00, 0x96d80433)
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot
mstore(0x40, m) // Restore the free memory pointer
}
}
/// @notice Withdraws the calling account's tokens from MaxApy's Vault, redeeming
/// amount of `shares` for the corresponding amount of tokens, which will be transferred to
/// `recipient` in the form of the chain's native token
/// @param vault The MaxApy vault to interact with
/// @param shares How many shares to try and redeem for tokens
/// @param recipient The address to issue the shares from MaxApy's Vault to
/// @param minAmountOut The minimum acceptable amount of assets to get in exchange for the burnt shares
/// @return amountOut The actual amount of redeemed assets
function redeemNative(
IMaxApyVault vault,
uint256 shares,
address recipient,
uint256 minAmountOut
)
external
returns (uint256 amountOut)
{
if (recipient == address(0)) revert InvalidRecipient();
// Cache `wrappedToken` and `vault` due to assembly's immutable access restrictions
address cachedWrappedToken = address(wrappedToken);
address cachedVault = address(vault);
assembly ("memory-safe") {
// Cache the free memory pointer
let m := mload(0x40)
// Store `vault`'s `redeem()` function selector:
// `bytes4(keccak256("redeem(uint256,address,address)"))`
mstore(0x00, 0xba087652)
mstore(0x20, shares) // append the `shares` argument
mstore(0x40, address()) // append the `recipient` argument
mstore(0x60, caller()) // append the `operator` argument
// Withdraw from MaxApy vault
if iszero(
call(
gas(), // Remaining amount of gas
cachedVault, // Address of `vault`
0, // `msg.value`
0x1c, // byte offset in memory where calldata starts
0x64, // size of the calldata to copy
0x00, // byte offset in memory to store the return data
0x20 // size of the return data
)
) {
// If call failed, throw the error thrown in the previous `call`
revert(0x00, 0x04)
}
// Store `amountOut` returned by the previous call to `withdraw()`
amountOut := mload(0x00)
if lt(amountOut, minAmountOut) {
// Throw the `InsufficientAssets` error
mstore(0x00, 0x96d80433)
revert(0x1c, 0x04)
}
// Store `wrappedToken`'s `withdraw()` function selector:
// `bytes4(keccak256("withdraw(uint256)"))`
mstore(0x00, 0x2e1a7d4d)
mstore(0x20, amountOut) // append the `amountOut` argument
// Withdraw from wrapped token
if iszero(
call(
gas(), // Remaining amount of gas
cachedWrappedToken, // Address of `vault`
0, // `msg.value`
0x1c, // byte offset in memory where calldata starts
0x24, // size of the calldata to copy
0x00, // byte offset in memory to store the return data
0x20 // size of the return data
)
) {
// If call failed, throw the error thrown in the previous `call`
revert(0x00, 0x04)
}
// Transfer native token back to user
if iszero(call(gas(), recipient, amountOut, 0x00, 0x00, 0x00, 0x00)) {
// If call failed, throw the `FailedNativeTransfer()` error
mstore(0x00, 0x3c3f4130)
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot
mstore(0x40, m) // Restore the free memory pointer
}
}
////////////////////////////////////////////////////////////////
/// Helper functions ///
////////////////////////////////////////////////////////////////
/// @dev helper function to perform ERC20 mas approvals to vaults
function _approveMax(address _vault, address _token) internal {
(bool s, bytes memory data) =
_token.staticcall(abi.encodeWithSignature("allowance(address,address)", address(this), _vault));
if (!s) revert();
uint256 _allowance = abi.decode(data, (uint256));
if (_allowance == 0) {
_token.safeApprove(_vault, type(uint256).max);
}
}
////////////////////////////////////////////////////////////////
/// RECEIVE() function ///
////////////////////////////////////////////////////////////////
/// @notice Receive function to accept native transfers
/// @dev Note only the chain's wrapped token will be able to perform native token transfers
/// to this contract
receive() external payable {
// Cache `wrappedToken` due to assembly immutable access restrictions
address cachedWrappedToken = address(wrappedToken);
assembly {
// Check if caller is not the `wrappedToken`
if iszero(eq(caller(), cachedWrappedToken)) {
// Throw the `ReceiveNotAllowed()` error
mstore(0x00, 0xcb263c3f)
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success :=
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(and(call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), exists)) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `1` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero(call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00)) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.19;
/// @notice Stores all data from a single strategy
/// @dev Packed in two slots
struct StrategyData {
/// Slot 0
/// @notice Maximum percentage available to be lent to strategies(in BPS)
/// @dev in BPS. uint16 is enough to cover the max BPS value of 10_000
uint16 strategyDebtRatio;
/// @notice The performance fee
/// @dev in BPS. uint16 is enough to cover the max BPS value of 10_000
uint16 strategyPerformanceFee;
/// @notice Timestamp when the strategy was added.
/// @dev Overflowing July 21, 2554
uint48 strategyActivation;
/// @notice block.timestamp of the last time a report occured
/// @dev Overflowing July 21, 2554
uint48 strategyLastReport;
/// @notice Upper limit on the increase of debt since last harvest
/// @dev max debt per harvest to be set to a maximum value of 4,722,366,482,869,645,213,695
uint128 strategyMaxDebtPerHarvest;
/// Slot 1
/// @notice Lower limit on the increase of debt since last harvest
/// @dev min debt per harvest to be set to a maximum value of 16,777,215
uint128 strategyMinDebtPerHarvest;
/// @notice Total returns that Strategy reported to the Vault
/// @dev max strategy total gain of 79,228,162,514,264,337,593,543,950,335
uint128 strategyTotalUnrealizedGain;
/// Slot 2
/// @notice Total outstanding debt that Strategy has
/// @dev max total debt of 79,228,162,514,264,337,593,543,950,335
uint128 strategyTotalDebt;
/// @notice Total losses that Strategy has realized for Vault
/// @dev max strategy total loss of 79,228,162,514,264,337,593,543,950,335
uint128 strategyTotalLoss;
/// Slot 3
/// @notice True if the strategy has switched to autopilot mode
/// @dev it is activated from the strategy and will only work if `autoPilotEnabled` == true
/// @dev the strategy can still be manually harvested in autopilot mode
bool autoPilot;
}
{
"compilationTarget": {
"src/MaxApyRouter.sol": "MaxApyRouter"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin-contracts-5.0.2/=dependencies/@openzeppelin-contracts-5.0.2/",
":forge-std-1.8.2/=dependencies/forge-std-1.8.2/src/",
":forge-std/=dependencies/forge-std-1.8.2/src/",
":openzeppelin/=dependencies/@openzeppelin-contracts-5.0.2/",
":solady-0.0.201/=dependencies/solady-0.0.201/src/",
":solady/=dependencies/solady-0.0.201/src/"
]
}
[{"inputs":[{"internalType":"contract IWrappedToken","name":"_wrappedToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"FailedNativeTransfer","type":"error"},{"inputs":[],"name":"InsufficientAssets","type":"error"},{"inputs":[],"name":"InsufficientShares","type":"error"},{"inputs":[],"name":"InvalidRecipient","type":"error"},{"inputs":[],"name":"ReceiveNotAllowed","type":"error"},{"inputs":[{"internalType":"contract IMaxApyVault","name":"vault","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"minSharesOut","type":"uint256"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"sharesOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IMaxApyVault","name":"vault","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"minSharesOut","type":"uint256"}],"name":"depositNative","outputs":[{"internalType":"uint256","name":"sharesOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IMaxApyVault","name":"vault","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"},{"internalType":"uint256","name":"minSharesOut","type":"uint256"}],"name":"depositWithPermit","outputs":[{"internalType":"uint256","name":"sharesOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IMaxApyVault","name":"vault","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"redeem","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IMaxApyVault","name":"vault","type":"address"},{"internalType":"uint256","name":"shares","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"minAmountOut","type":"uint256"}],"name":"redeemNative","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wrappedToken","outputs":[{"internalType":"contract IWrappedToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]