Contract Source Code:
File 1 of 1 : FLAction
// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;
contract MainnetActionsUtilAddresses {
address internal constant DFS_REG_CONTROLLER_ADDR = 0xF8f8B3C98Cf2E63Df3041b73f80F362a4cf3A576;
address internal constant REGISTRY_ADDR = 0x287778F121F134C66212FB16c9b53eC991D32f5b;
address internal constant DFS_LOGGER_ADDR = 0xcE7a977Cac4a481bc84AC06b2Da0df614e621cf3;
address internal constant SUB_STORAGE_ADDR = 0x1612fc28Ee0AB882eC99842Cde0Fc77ff0691e90;
address internal constant PROXY_AUTH_ADDR = 0x149667b6FAe2c63D1B4317C716b0D0e4d3E2bD70;
address internal constant LSV_PROXY_REGISTRY_ADDRESS = 0xa8a3c86c4A2DcCf350E84D2b3c46BDeBc711C16e;
address internal constant TRANSIENT_STORAGE = 0x2F7Ef2ea5E8c97B8687CA703A0e50Aa5a49B7eb2;
}
contract ActionsUtilHelper is MainnetActionsUtilAddresses {
}
contract MainnetAuthAddresses {
address internal constant ADMIN_VAULT_ADDR = 0xCCf3d848e08b94478Ed8f46fFead3008faF581fD;
address internal constant DSGUARD_FACTORY_ADDRESS = 0x5a15566417e6C1c9546523066500bDDBc53F88C7;
address internal constant ADMIN_ADDR = 0x25eFA336886C74eA8E282ac466BdCd0199f85BB9; // USED IN ADMIN VAULT CONSTRUCTOR
address internal constant PROXY_AUTH_ADDRESS = 0x149667b6FAe2c63D1B4317C716b0D0e4d3E2bD70;
address internal constant MODULE_AUTH_ADDRESS = 0x7407974DDBF539e552F1d051e44573090912CC3D;
}
contract AuthHelper is MainnetAuthAddresses {
}
contract AdminVault is AuthHelper {
address public owner;
address public admin;
error SenderNotAdmin();
constructor() {
owner = msg.sender;
admin = ADMIN_ADDR;
}
/// @notice Admin is able to change owner
/// @param _owner Address of new owner
function changeOwner(address _owner) public {
if (admin != msg.sender){
revert SenderNotAdmin();
}
owner = _owner;
}
/// @notice Admin is able to set new admin
/// @param _admin Address of multisig that becomes new admin
function changeAdmin(address _admin) public {
if (admin != msg.sender){
revert SenderNotAdmin();
}
admin = _admin;
}
}
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint256 digits);
function totalSupply() external view returns (uint256 supply);
function balanceOf(address _owner) external view returns (uint256 balance);
function transfer(address _to, uint256 _value) external returns (bool success);
function transferFrom(
address _from,
address _to,
uint256 _value
) external returns (bool success);
function approve(address _spender, uint256 _value) external returns (bool success);
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
library Address {
//insufficient balance
error InsufficientBalance(uint256 available, uint256 required);
//unable to send value, recipient may have reverted
error SendingValueFail();
//insufficient balance for call
error InsufficientBalanceForCall(uint256 available, uint256 required);
//call to non-contract
error NonContractCall();
function isContract(address account) internal view returns (bool) {
// According to EIP-1052, 0x0 is the value returned for not-yet created accounts
// and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
// solhint-disable-next-line no-inline-assembly
assembly {
codehash := extcodehash(account)
}
return (codehash != accountHash && codehash != 0x0);
}
function sendValue(address payable recipient, uint256 amount) internal {
uint256 balance = address(this).balance;
if (balance < amount){
revert InsufficientBalance(balance, amount);
}
// solhint-disable-next-line avoid-low-level-calls, avoid-call-value
(bool success, ) = recipient.call{value: amount}("");
if (!(success)){
revert SendingValueFail();
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return _functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return
functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
uint256 balance = address(this).balance;
if (balance < value){
revert InsufficientBalanceForCall(balance, value);
}
return _functionCallWithValue(target, data, value, errorMessage);
}
function _functionCallWithValue(
address target,
bytes memory data,
uint256 weiValue,
string memory errorMessage
) private returns (bytes memory) {
if (!(isContract(target))){
revert NonContractCall();
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = target.call{value: weiValue}(data);
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
* 0 before setting it to a non-zero value.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
contract AdminAuth is AuthHelper {
using SafeERC20 for IERC20;
AdminVault public constant adminVault = AdminVault(ADMIN_VAULT_ADDR);
error SenderNotOwner();
error SenderNotAdmin();
modifier onlyOwner() {
if (adminVault.owner() != msg.sender){
revert SenderNotOwner();
}
_;
}
modifier onlyAdmin() {
if (adminVault.admin() != msg.sender){
revert SenderNotAdmin();
}
_;
}
/// @notice withdraw stuck funds
function withdrawStuckFunds(address _token, address _receiver, uint256 _amount) public onlyOwner {
if (_token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) {
payable(_receiver).transfer(_amount);
} else {
IERC20(_token).safeTransfer(_receiver, _amount);
}
}
/// @notice Destroy the contract
/// @dev Deprecated method, selfdestruct will soon just send eth
function kill() public onlyAdmin {
selfdestruct(payable(msg.sender));
}
}
contract DFSRegistry is AdminAuth {
error EntryAlreadyExistsError(bytes4);
error EntryNonExistentError(bytes4);
error EntryNotInChangeError(bytes4);
error ChangeNotReadyError(uint256,uint256);
error EmptyPrevAddrError(bytes4);
error AlreadyInContractChangeError(bytes4);
error AlreadyInWaitPeriodChangeError(bytes4);
event AddNewContract(address,bytes4,address,uint256);
event RevertToPreviousAddress(address,bytes4,address,address);
event StartContractChange(address,bytes4,address,address);
event ApproveContractChange(address,bytes4,address,address);
event CancelContractChange(address,bytes4,address,address);
event StartWaitPeriodChange(address,bytes4,uint256);
event ApproveWaitPeriodChange(address,bytes4,uint256,uint256);
event CancelWaitPeriodChange(address,bytes4,uint256,uint256);
struct Entry {
address contractAddr;
uint256 waitPeriod;
uint256 changeStartTime;
bool inContractChange;
bool inWaitPeriodChange;
bool exists;
}
mapping(bytes4 => Entry) public entries;
mapping(bytes4 => address) public previousAddresses;
mapping(bytes4 => address) public pendingAddresses;
mapping(bytes4 => uint256) public pendingWaitTimes;
/// @notice Given an contract id returns the registered address
/// @dev Id is keccak256 of the contract name
/// @param _id Id of contract
function getAddr(bytes4 _id) public view returns (address) {
return entries[_id].contractAddr;
}
/// @notice Helper function to easily query if id is registered
/// @param _id Id of contract
function isRegistered(bytes4 _id) public view returns (bool) {
return entries[_id].exists;
}
/////////////////////////// OWNER ONLY FUNCTIONS ///////////////////////////
/// @notice Adds a new contract to the registry
/// @param _id Id of contract
/// @param _contractAddr Address of the contract
/// @param _waitPeriod Amount of time to wait before a contract address can be changed
function addNewContract(
bytes4 _id,
address _contractAddr,
uint256 _waitPeriod
) public onlyOwner {
if (entries[_id].exists){
revert EntryAlreadyExistsError(_id);
}
entries[_id] = Entry({
contractAddr: _contractAddr,
waitPeriod: _waitPeriod,
changeStartTime: 0,
inContractChange: false,
inWaitPeriodChange: false,
exists: true
});
emit AddNewContract(msg.sender, _id, _contractAddr, _waitPeriod);
}
/// @notice Reverts to the previous address immediately
/// @dev In case the new version has a fault, a quick way to fallback to the old contract
/// @param _id Id of contract
function revertToPreviousAddress(bytes4 _id) public onlyOwner {
if (!(entries[_id].exists)){
revert EntryNonExistentError(_id);
}
if (previousAddresses[_id] == address(0)){
revert EmptyPrevAddrError(_id);
}
address currentAddr = entries[_id].contractAddr;
entries[_id].contractAddr = previousAddresses[_id];
emit RevertToPreviousAddress(msg.sender, _id, currentAddr, previousAddresses[_id]);
}
/// @notice Starts an address change for an existing entry
/// @dev Can override a change that is currently in progress
/// @param _id Id of contract
/// @param _newContractAddr Address of the new contract
function startContractChange(bytes4 _id, address _newContractAddr) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (entries[_id].inWaitPeriodChange){
revert AlreadyInWaitPeriodChangeError(_id);
}
entries[_id].changeStartTime = block.timestamp; // solhint-disable-line
entries[_id].inContractChange = true;
pendingAddresses[_id] = _newContractAddr;
emit StartContractChange(msg.sender, _id, entries[_id].contractAddr, _newContractAddr);
}
/// @notice Changes new contract address, correct time must have passed
/// @param _id Id of contract
function approveContractChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inContractChange){
revert EntryNotInChangeError(_id);
}
if (block.timestamp < (entries[_id].changeStartTime + entries[_id].waitPeriod)){// solhint-disable-line
revert ChangeNotReadyError(block.timestamp, (entries[_id].changeStartTime + entries[_id].waitPeriod));
}
address oldContractAddr = entries[_id].contractAddr;
entries[_id].contractAddr = pendingAddresses[_id];
entries[_id].inContractChange = false;
entries[_id].changeStartTime = 0;
pendingAddresses[_id] = address(0);
previousAddresses[_id] = oldContractAddr;
emit ApproveContractChange(msg.sender, _id, oldContractAddr, entries[_id].contractAddr);
}
/// @notice Cancel pending change
/// @param _id Id of contract
function cancelContractChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inContractChange){
revert EntryNotInChangeError(_id);
}
address oldContractAddr = pendingAddresses[_id];
pendingAddresses[_id] = address(0);
entries[_id].inContractChange = false;
entries[_id].changeStartTime = 0;
emit CancelContractChange(msg.sender, _id, oldContractAddr, entries[_id].contractAddr);
}
/// @notice Starts the change for waitPeriod
/// @param _id Id of contract
/// @param _newWaitPeriod New wait time
function startWaitPeriodChange(bytes4 _id, uint256 _newWaitPeriod) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (entries[_id].inContractChange){
revert AlreadyInContractChangeError(_id);
}
pendingWaitTimes[_id] = _newWaitPeriod;
entries[_id].changeStartTime = block.timestamp; // solhint-disable-line
entries[_id].inWaitPeriodChange = true;
emit StartWaitPeriodChange(msg.sender, _id, _newWaitPeriod);
}
/// @notice Changes new wait period, correct time must have passed
/// @param _id Id of contract
function approveWaitPeriodChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inWaitPeriodChange){
revert EntryNotInChangeError(_id);
}
if (block.timestamp < (entries[_id].changeStartTime + entries[_id].waitPeriod)){ // solhint-disable-line
revert ChangeNotReadyError(block.timestamp, (entries[_id].changeStartTime + entries[_id].waitPeriod));
}
uint256 oldWaitTime = entries[_id].waitPeriod;
entries[_id].waitPeriod = pendingWaitTimes[_id];
entries[_id].inWaitPeriodChange = false;
entries[_id].changeStartTime = 0;
pendingWaitTimes[_id] = 0;
emit ApproveWaitPeriodChange(msg.sender, _id, oldWaitTime, entries[_id].waitPeriod);
}
/// @notice Cancel wait period change
/// @param _id Id of contract
function cancelWaitPeriodChange(bytes4 _id) public onlyOwner {
if (!entries[_id].exists){
revert EntryNonExistentError(_id);
}
if (!entries[_id].inWaitPeriodChange){
revert EntryNotInChangeError(_id);
}
uint256 oldWaitPeriod = pendingWaitTimes[_id];
pendingWaitTimes[_id] = 0;
entries[_id].inWaitPeriodChange = false;
entries[_id].changeStartTime = 0;
emit CancelWaitPeriodChange(msg.sender, _id, oldWaitPeriod, entries[_id].waitPeriod);
}
}
abstract contract DSAuthority {
function canCall(
address src,
address dst,
bytes4 sig
) public view virtual returns (bool);
}
contract DSAuthEvents {
event LogSetAuthority(address indexed authority);
event LogSetOwner(address indexed owner);
}
contract DSAuth is DSAuthEvents {
DSAuthority public authority;
address public owner;
constructor() {
owner = msg.sender;
emit LogSetOwner(msg.sender);
}
function setOwner(address owner_) public auth {
owner = owner_;
emit LogSetOwner(owner);
}
function setAuthority(DSAuthority authority_) public auth {
authority = authority_;
emit LogSetAuthority(address(authority));
}
modifier auth {
require(isAuthorized(msg.sender, msg.sig), "Not authorized");
_;
}
function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
if (src == address(this)) {
return true;
} else if (src == owner) {
return true;
} else if (authority == DSAuthority(address(0))) {
return false;
} else {
return authority.canCall(src, address(this), sig);
}
}
}
contract DSNote {
event LogNote(
bytes4 indexed sig,
address indexed guy,
bytes32 indexed foo,
bytes32 indexed bar,
uint256 wad,
bytes fax
) anonymous;
modifier note {
bytes32 foo;
bytes32 bar;
assembly {
foo := calldataload(4)
bar := calldataload(36)
}
emit LogNote(msg.sig, msg.sender, foo, bar, msg.value, msg.data);
_;
}
}
abstract contract DSProxy is DSAuth, DSNote {
DSProxyCache public cache; // global cache for contracts
constructor(address _cacheAddr) {
if (!(setCache(_cacheAddr))){
require(isAuthorized(msg.sender, msg.sig), "Not authorized");
}
}
// solhint-disable-next-line no-empty-blocks
receive() external payable {}
// use the proxy to execute calldata _data on contract _code
function execute(bytes memory _code, bytes memory _data)
public
payable
virtual
returns (address target, bytes32 response);
function execute(address _target, bytes memory _data)
public
payable
virtual
returns (bytes32 response);
//set new cache
function setCache(address _cacheAddr) public payable virtual returns (bool);
}
contract DSProxyCache {
mapping(bytes32 => address) cache;
function read(bytes memory _code) public view returns (address) {
bytes32 hash = keccak256(_code);
return cache[hash];
}
function write(bytes memory _code) public returns (address target) {
assembly {
target := create(0, add(_code, 0x20), mload(_code))
switch iszero(extcodesize(target))
case 1 {
// throw if contract failed to deploy
revert(0, 0)
}
}
bytes32 hash = keccak256(_code);
cache[hash] = target;
}
}
interface ISafe {
enum Operation {
Call,
DelegateCall
}
function setup(
address[] calldata _owners,
uint256 _threshold,
address to,
bytes calldata data,
address fallbackHandler,
address paymentToken,
uint256 payment,
address payable paymentReceiver
) external;
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) external payable returns (bool success);
function execTransactionFromModule(
address to,
uint256 value,
bytes memory data,
Operation operation
) external returns (bool success);
function checkSignatures(
bytes32 dataHash,
bytes memory data,
bytes memory signatures
) external view;
function checkNSignatures(
address executor,
bytes32 dataHash,
bytes memory /* data */,
bytes memory signatures,
uint256 requiredSignatures
) external view;
function approveHash(bytes32 hashToApprove) external;
function domainSeparator() external view returns (bytes32);
function getTransactionHash(
address to,
uint256 value,
bytes calldata data,
Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes32);
function nonce() external view returns (uint256);
function setFallbackHandler(address handler) external;
function getOwners() external view returns (address[] memory);
function isOwner(address owner) external view returns (bool);
function getThreshold() external view returns (uint256);
function enableModule(address module) external;
function isModuleEnabled(address module) external view returns (bool);
function disableModule(address prevModule, address module) external;
function getModulesPaginated(
address start,
uint256 pageSize
) external view returns (address[] memory array, address next);
}
interface IDSProxyFactory {
function isProxy(address _proxy) external view returns (bool);
}
contract MainnetProxyFactoryAddresses {
address internal constant PROXY_FACTORY_ADDR = 0xA26e15C895EFc0616177B7c1e7270A4C7D51C997;
}
contract DSProxyFactoryHelper is MainnetProxyFactoryAddresses {
}
contract CheckWalletType is DSProxyFactoryHelper {
function isDSProxy(address _proxy) public view returns (bool) {
return IDSProxyFactory(PROXY_FACTORY_ADDR).isProxy(_proxy);
}
}
contract DefisaverLogger {
event RecipeEvent(
address indexed caller,
string indexed logName
);
event ActionDirectEvent(
address indexed caller,
string indexed logName,
bytes data
);
function logRecipeEvent(
string memory _logName
) public {
emit RecipeEvent(msg.sender, _logName);
}
function logActionDirectEvent(
string memory _logName,
bytes memory _data
) public {
emit ActionDirectEvent(msg.sender, _logName, _data);
}
}
abstract contract ActionBase is AdminAuth, ActionsUtilHelper, CheckWalletType {
event ActionEvent(
string indexed logName,
bytes data
);
DFSRegistry public constant registry = DFSRegistry(REGISTRY_ADDR);
DefisaverLogger public constant logger = DefisaverLogger(
DFS_LOGGER_ADDR
);
//Wrong sub index value
error SubIndexValueError();
//Wrong return index value
error ReturnIndexValueError();
/// @dev Subscription params index range [128, 255]
uint8 public constant SUB_MIN_INDEX_VALUE = 128;
uint8 public constant SUB_MAX_INDEX_VALUE = 255;
/// @dev Return params index range [1, 127]
uint8 public constant RETURN_MIN_INDEX_VALUE = 1;
uint8 public constant RETURN_MAX_INDEX_VALUE = 127;
/// @dev If the input value should not be replaced
uint8 public constant NO_PARAM_MAPPING = 0;
/// @dev We need to parse Flash loan actions in a different way
enum ActionType { FL_ACTION, STANDARD_ACTION, FEE_ACTION, CHECK_ACTION, CUSTOM_ACTION }
/// @notice Parses inputs and runs the implemented action through a user wallet
/// @dev Is called by the RecipeExecutor chaining actions together
/// @param _callData Array of input values each value encoded as bytes
/// @param _subData Array of subscribed vales, replaces input values if specified
/// @param _paramMapping Array that specifies how return and subscribed values are mapped in input
/// @param _returnValues Returns values from actions before, which can be injected in inputs
/// @return Returns a bytes32 value through user wallet, each actions implements what that value is
function executeAction(
bytes memory _callData,
bytes32[] memory _subData,
uint8[] memory _paramMapping,
bytes32[] memory _returnValues
) public payable virtual returns (bytes32);
/// @notice Parses inputs and runs the single implemented action through a user wallet
/// @dev Used to save gas when executing a single action directly
function executeActionDirect(bytes memory _callData) public virtual payable;
/// @notice Returns the type of action we are implementing
function actionType() public pure virtual returns (uint8);
//////////////////////////// HELPER METHODS ////////////////////////////
/// @notice Given an uint256 input, injects return/sub values if specified
/// @param _param The original input value
/// @param _mapType Indicated the type of the input in paramMapping
/// @param _subData Array of subscription data we can replace the input value with
/// @param _returnValues Array of subscription data we can replace the input value with
function _parseParamUint(
uint _param,
uint8 _mapType,
bytes32[] memory _subData,
bytes32[] memory _returnValues
) internal pure returns (uint) {
if (isReplaceable(_mapType)) {
if (isReturnInjection(_mapType)) {
_param = uint(_returnValues[getReturnIndex(_mapType)]);
} else {
_param = uint256(_subData[getSubIndex(_mapType)]);
}
}
return _param;
}
/// @notice Given an addr input, injects return/sub values if specified
/// @param _param The original input value
/// @param _mapType Indicated the type of the input in paramMapping
/// @param _subData Array of subscription data we can replace the input value with
/// @param _returnValues Array of subscription data we can replace the input value with
function _parseParamAddr(
address _param,
uint8 _mapType,
bytes32[] memory _subData,
bytes32[] memory _returnValues
) internal view returns (address) {
if (isReplaceable(_mapType)) {
if (isReturnInjection(_mapType)) {
_param = address(bytes20((_returnValues[getReturnIndex(_mapType)])));
} else {
/// @dev The last two values are specially reserved for proxy addr and owner addr
if (_mapType == 254) return address(this); // wallet address
if (_mapType == 255) return fetchOwnersOrWallet(); // owner if 1/1 wallet or the wallet itself
_param = address(uint160(uint256(_subData[getSubIndex(_mapType)])));
}
}
return _param;
}
/// @notice Given an bytes32 input, injects return/sub values if specified
/// @param _param The original input value
/// @param _mapType Indicated the type of the input in paramMapping
/// @param _subData Array of subscription data we can replace the input value with
/// @param _returnValues Array of subscription data we can replace the input value with
function _parseParamABytes32(
bytes32 _param,
uint8 _mapType,
bytes32[] memory _subData,
bytes32[] memory _returnValues
) internal pure returns (bytes32) {
if (isReplaceable(_mapType)) {
if (isReturnInjection(_mapType)) {
_param = (_returnValues[getReturnIndex(_mapType)]);
} else {
_param = _subData[getSubIndex(_mapType)];
}
}
return _param;
}
/// @notice Checks if the paramMapping value indicated that we need to inject values
/// @param _type Indicated the type of the input
function isReplaceable(uint8 _type) internal pure returns (bool) {
return _type != NO_PARAM_MAPPING;
}
/// @notice Checks if the paramMapping value is in the return value range
/// @param _type Indicated the type of the input
function isReturnInjection(uint8 _type) internal pure returns (bool) {
return (_type >= RETURN_MIN_INDEX_VALUE) && (_type <= RETURN_MAX_INDEX_VALUE);
}
/// @notice Transforms the paramMapping value to the index in return array value
/// @param _type Indicated the type of the input
function getReturnIndex(uint8 _type) internal pure returns (uint8) {
if (!(isReturnInjection(_type))){
revert SubIndexValueError();
}
return (_type - RETURN_MIN_INDEX_VALUE);
}
/// @notice Transforms the paramMapping value to the index in sub array value
/// @param _type Indicated the type of the input
function getSubIndex(uint8 _type) internal pure returns (uint8) {
if (_type < SUB_MIN_INDEX_VALUE){
revert ReturnIndexValueError();
}
return (_type - SUB_MIN_INDEX_VALUE);
}
function fetchOwnersOrWallet() internal view returns (address) {
if (isDSProxy(address(this)))
return DSProxy(payable(address(this))).owner();
// if not DSProxy, we assume we are in context of Safe
address[] memory owners = ISafe(address(this)).getOwners();
return owners.length == 1 ? owners[0] : address(this);
}
}
contract MainnetFLAddresses {
address internal constant SOLO_MARGIN_ADDRESS = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e;
address internal constant ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address internal constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address internal constant DYDX_FL_FEE_FAUCET = 0x47f159C90850D5cE09E21F931d504536840f34b4;
address internal constant AAVE_LENDING_POOL = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9;
address internal constant AAVE_LENDING_POOL_ADDRESS_PROVIDER = 0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5;
address internal constant AAVE_V3_LENDING_POOL = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
address internal constant AAVE_V3_LENDING_POOL_ADDRESS_PROVIDER = 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e;
address internal constant SPARK_LENDING_POOL_ADDRESS_PROVIDER = 0x02C3eA4e34C0cBd694D2adFa2c690EECbC1793eE;
address internal constant SPARK_LENDING_POOL = 0xC13e21B648A5Ee794902342038FF3aDAB66BE987;
address internal constant DSS_FLASH_ADDR = 0x60744434d6339a6B27d73d9Eda62b6F66a0a04FA;
address internal constant DAI_ADDR = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address internal constant ST_ETH_ADDR = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;
address internal constant VAULT_ADDR = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
address internal constant GHO_ADDR = 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f;
address internal constant GHO_FLASH_MINTER_ADDR = 0xb639D208Bcf0589D54FaC24E655C79EC529762B8;
address internal constant UNI_V3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
address internal constant MORPHO_BLUE_ADDR = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb;
address internal constant CURVEUSD_FLASH_ADDR = 0xA7a4bb50AF91f90b6fEb3388E7f8286aF45b299B;
address internal constant CURVEUSD_ADDR = 0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E;
address internal constant RECIPE_EXECUTOR_ADDR = 0x5029336642814bC51a42bA80BF83a6322110035D;
}
contract StrategyModel {
/// @dev Group of strategies bundled together so user can sub to multiple strategies at once
/// @param creator Address of the user who created the bundle
/// @param strategyIds Array of strategy ids stored in StrategyStorage
struct StrategyBundle {
address creator;
uint64[] strategyIds;
}
/// @dev Template/Class which defines a Strategy
/// @param name Name of the strategy useful for logging what strategy is executing
/// @param creator Address of the user which created the strategy
/// @param triggerIds Array of identifiers for trigger - bytes4(keccak256(TriggerName))
/// @param actionIds Array of identifiers for actions - bytes4(keccak256(ActionName))
/// @param paramMapping Describes how inputs to functions are piped from return/subbed values
/// @param continuous If the action is repeated (continuos) or one time
struct Strategy {
string name;
address creator;
bytes4[] triggerIds;
bytes4[] actionIds;
uint8[][] paramMapping;
bool continuous;
}
/// @dev List of actions grouped as a recipe
/// @param name Name of the recipe useful for logging what recipe is executing
/// @param callData Array of calldata inputs to each action
/// @param subData Used only as part of strategy, subData injected from StrategySub.subData
/// @param actionIds Array of identifiers for actions - bytes4(keccak256(ActionName))
/// @param paramMapping Describes how inputs to functions are piped from return/subbed values
struct Recipe {
string name;
bytes[] callData;
bytes32[] subData;
bytes4[] actionIds;
uint8[][] paramMapping;
}
/// @dev Actual data of the sub we store on-chain
/// @dev In order to save on gas we store a keccak256(StrategySub) and verify later on
/// @param walletAddr Address of the users smart wallet/proxy
/// @param isEnabled Toggle if the subscription is active
/// @param strategySubHash Hash of the StrategySub data the user inputted
struct StoredSubData {
bytes20 walletAddr; // address but put in bytes20 for gas savings
bool isEnabled;
bytes32 strategySubHash;
}
/// @dev Instance of a strategy, user supplied data
/// @param strategyOrBundleId Id of the strategy or bundle, depending on the isBundle bool
/// @param isBundle If true the id points to bundle, if false points directly to strategyId
/// @param triggerData User supplied data needed for checking trigger conditions
/// @param subData User supplied data used in recipe
struct StrategySub {
uint64 strategyOrBundleId;
bool isBundle;
bytes[] triggerData;
bytes32[] subData;
}
/// @dev Data needed when signing relay transaction
/// @param maxTxCostInFeeToken Max tx cost user is willing to pay in fee token
/// @param feeToken Address of the token user is willing to pay fee in
/// @param tokenPriceInEth Price of the token in ETH
/// @param deadline Deadline for the relay transaction to be executed
/// @param shouldTakeFeeFromPosition Flag to indicate if fee should be taken from position, otherwise from EOA/wallet
struct TxSaverSignedData {
uint256 maxTxCostInFeeToken;
address feeToken;
uint256 tokenPriceInEth;
uint256 deadline;
bool shouldTakeFeeFromPosition;
}
}
abstract contract IDSProxy {
// function execute(bytes memory _code, bytes memory _data)
// public
// payable
// virtual
// returns (address, bytes32);
function execute(address _target, bytes memory _data) public payable virtual returns (bytes32);
function setCache(address _cacheAddr) public payable virtual returns (bool);
function owner() public view virtual returns (address);
}
contract FLFeeFaucet {
using SafeERC20 for IERC20;
/// @notice Sends 2 wei to msg.sender
/// @dev Anyone can call this method but it's not economically feasible to drain
/// @param _tokenAddr Address of the token we want 2 wei
function my2Wei(address _tokenAddr) public {
IERC20(_tokenAddr).safeTransfer(msg.sender, 2);
}
}
contract FLHelper is MainnetFLAddresses, StrategyModel {
uint16 internal constant AAVE_REFERRAL_CODE = 64;
uint16 internal constant SPARK_REFERRAL_CODE = 0;
FLFeeFaucet public constant flFeeFaucet = FLFeeFaucet(DYDX_FL_FEE_FAUCET);
/// @dev Function sig of RecipeExecutor._executeActionsFromFL()
bytes4 public constant CALLBACK_SELECTOR =
bytes4(
keccak256(
"_executeActionsFromFL((string,bytes[],bytes32[],bytes4[],uint8[][]),bytes32)"
)
);
// Revert if execution fails when using safe wallet
error SafeExecutionError();
function _executeRecipe(address _wallet, bool _isDSProxy, Recipe memory _currRecipe, uint256 _paybackAmount) internal {
if (_isDSProxy) {
IDSProxy(_wallet).execute{value: address(this).balance}(
RECIPE_EXECUTOR_ADDR,
abi.encodeWithSelector(CALLBACK_SELECTOR, _currRecipe, _paybackAmount)
);
} else {
bool success = ISafe(_wallet).execTransactionFromModule(
RECIPE_EXECUTOR_ADDR,
address(this).balance,
abi.encodeWithSelector(CALLBACK_SELECTOR, _currRecipe, _paybackAmount),
ISafe.Operation.DelegateCall
);
if (!success) {
revert SafeExecutionError();
}
}
}
}
interface ILendingPoolAddressesProviderV2 {
event LendingPoolUpdated(address indexed newAddress);
event ConfigurationAdminUpdated(address indexed newAddress);
event EmergencyAdminUpdated(address indexed newAddress);
event LendingPoolConfiguratorUpdated(address indexed newAddress);
event LendingPoolCollateralManagerUpdated(address indexed newAddress);
event PriceOracleUpdated(address indexed newAddress);
event LendingRateOracleUpdated(address indexed newAddress);
event ProxyCreated(bytes32 id, address indexed newAddress);
event AddressSet(bytes32 id, address indexed newAddress, bool hasProxy);
function setAddress(bytes32 id, address newAddress) external;
function setAddressAsProxy(bytes32 id, address impl) external;
function getAddress(bytes32 id) external view returns (address);
function getLendingPool() external view returns (address);
function setLendingPoolImpl(address pool) external;
function getLendingPoolConfigurator() external view returns (address);
function setLendingPoolConfiguratorImpl(address configurator) external;
function getLendingPoolCollateralManager() external view returns (address);
function setLendingPoolCollateralManager(address manager) external;
function getPoolAdmin() external view returns (address);
function setPoolAdmin(address admin) external;
function getEmergencyAdmin() external view returns (address);
function setEmergencyAdmin(address admin) external;
function getPriceOracle() external view returns (address);
function setPriceOracle(address priceOracle) external;
function getLendingRateOracle() external view returns (address);
function setLendingRateOracle(address lendingRateOracle) external;
}
library DataTypes {
// refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties.
struct ReserveData {
//stores the reserve configuration
ReserveConfigurationMap configuration;
//the liquidity index. Expressed in ray
uint128 liquidityIndex;
//variable borrow index. Expressed in ray
uint128 variableBorrowIndex;
//the current supply rate. Expressed in ray
uint128 currentLiquidityRate;
//the current variable borrow rate. Expressed in ray
uint128 currentVariableBorrowRate;
//the current stable borrow rate. Expressed in ray
uint128 currentStableBorrowRate;
uint40 lastUpdateTimestamp;
//tokens addresses
address aTokenAddress;
address stableDebtTokenAddress;
address variableDebtTokenAddress;
//address of the interest rate strategy
address interestRateStrategyAddress;
//the id of the reserve. Represents the position in the list of the active reserves
uint8 id;
}
struct ReserveConfigurationMap {
//bit 0-15: LTV
//bit 16-31: Liq. threshold
//bit 32-47: Liq. bonus
//bit 48-55: Decimals
//bit 56: Reserve is active
//bit 57: reserve is frozen
//bit 58: borrowing is enabled
//bit 59: stable rate borrowing enabled
//bit 60-63: reserved
//bit 64-79: reserve factor
uint256 data;
}
struct UserConfigurationMap {
uint256 data;
}
enum InterestRateMode {NONE, STABLE, VARIABLE}
}
interface ILendingPoolV2 {
/**
* @dev Emitted on deposit()
* @param reserve The address of the underlying asset of the reserve
* @param user The address initiating the deposit
* @param onBehalfOf The beneficiary of the deposit, receiving the aTokens
* @param amount The amount deposited
* @param referral The referral code used
**/
event Deposit(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint16 indexed referral
);
/**
* @dev Emitted on withdraw()
* @param reserve The address of the underlyng asset being withdrawn
* @param user The address initiating the withdrawal, owner of aTokens
* @param to Address that will receive the underlying
* @param amount The amount to be withdrawn
**/
event Withdraw(address indexed reserve, address indexed user, address indexed to, uint256 amount);
/**
* @dev Emitted on borrow() and flashLoan() when debt needs to be opened
* @param reserve The address of the underlying asset being borrowed
* @param user The address of the user initiating the borrow(), receiving the funds on borrow() or just
* initiator of the transaction on flashLoan()
* @param onBehalfOf The address that will be getting the debt
* @param amount The amount borrowed out
* @param borrowRateMode The rate mode: 1 for Stable, 2 for Variable
* @param borrowRate The numeric rate at which the user has borrowed
* @param referral The referral code used
**/
event Borrow(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint256 borrowRateMode,
uint256 borrowRate,
uint16 indexed referral
);
/**
* @dev Emitted on repay()
* @param reserve The address of the underlying asset of the reserve
* @param user The beneficiary of the repayment, getting his debt reduced
* @param repayer The address of the user initiating the repay(), providing the funds
* @param amount The amount repaid
**/
event Repay(
address indexed reserve,
address indexed user,
address indexed repayer,
uint256 amount
);
/**
* @dev Emitted on swapBorrowRateMode()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user swapping his rate mode
* @param rateMode The rate mode that the user wants to swap to
**/
event Swap(address indexed reserve, address indexed user, uint256 rateMode);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
**/
event ReserveUsedAsCollateralEnabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on setUserUseReserveAsCollateral()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user enabling the usage as collateral
**/
event ReserveUsedAsCollateralDisabled(address indexed reserve, address indexed user);
/**
* @dev Emitted on rebalanceStableBorrowRate()
* @param reserve The address of the underlying asset of the reserve
* @param user The address of the user for which the rebalance has been executed
**/
event RebalanceStableBorrowRate(address indexed reserve, address indexed user);
/**
* @dev Emitted on flashLoan()
* @param target The address of the flash loan receiver contract
* @param initiator The address initiating the flash loan
* @param asset The address of the asset being flash borrowed
* @param amount The amount flash borrowed
* @param premium The fee flash borrowed
* @param referralCode The referral code used
**/
event FlashLoan(
address indexed target,
address indexed initiator,
address indexed asset,
uint256 amount,
uint256 premium,
uint16 referralCode
);
/**
* @dev Emitted when the pause is triggered.
*/
event Paused();
/**
* @dev Emitted when the pause is lifted.
*/
event Unpaused();
/**
* @dev Emitted when a borrower is liquidated. This event is emitted by the LendingPool via
* LendingPoolCollateral manager using a DELEGATECALL
* This allows to have the events in the generated ABI for LendingPool.
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param liquidatedCollateralAmount The amount of collateral received by the liiquidator
* @param liquidator The address of the liquidator
* @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
**/
event LiquidationCall(
address indexed collateralAsset,
address indexed debtAsset,
address indexed user,
uint256 debtToCover,
uint256 liquidatedCollateralAmount,
address liquidator,
bool receiveAToken
);
/**
* @dev Emitted when the state of a reserve is updated. NOTE: This event is actually declared
* in the ReserveLogic library and emitted in the updateInterestRates() function. Since the function is internal,
* the event will actually be fired by the LendingPool contract. The event is therefore replicated here so it
* gets added to the LendingPool ABI
* @param reserve The address of the underlying asset of the reserve
* @param liquidityRate The new liquidity rate
* @param stableBorrowRate The new stable borrow rate
* @param variableBorrowRate The new variable borrow rate
* @param liquidityIndex The new liquidity index
* @param variableBorrowIndex The new variable borrow index
**/
event ReserveDataUpdated(
address indexed reserve,
uint256 liquidityRate,
uint256 stableBorrowRate,
uint256 variableBorrowRate,
uint256 liquidityIndex,
uint256 variableBorrowIndex
);
/**
* @dev Deposits an `amount` of underlying asset into the reserve, receiving in return overlying aTokens.
* - E.g. User deposits 100 USDC and gets in return 100 aUSDC
* @param asset The address of the underlying asset to deposit
* @param amount The amount to be deposited
* @param onBehalfOf The address that will receive the aTokens, same as msg.sender if the user
* wants to receive them on his own wallet, or a different address if the beneficiary of aTokens
* is a different wallet
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function deposit(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @dev Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to Address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
**/
function withdraw(
address asset,
uint256 amount,
address to
) external;
/**
* @dev Allows users to borrow a specific `amount` of the reserve underlying asset, provided that the borrower
* already deposited enough collateral, or he was given enough allowance by a credit delegator on the
* corresponding debt token (StableDebtToken or VariableDebtToken)
* - E.g. User borrows 100 USDC passing as `onBehalfOf` his own address, receiving the 100 USDC in his wallet
* and 100 stable/variable debt tokens, depending on the `interestRateMode`
* @param asset The address of the underlying asset to borrow
* @param amount The amount to be borrowed
* @param interestRateMode The interest rate mode at which the user wants to borrow: 1 for Stable, 2 for Variable
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
* @param onBehalfOf Address of the user who will receive the debt. Should be the address of the borrower itself
* calling the function if he wants to borrow against his own collateral, or the address of the credit delegator
* if he has been given credit delegation allowance
**/
function borrow(
address asset,
uint256 amount,
uint256 interestRateMode,
uint16 referralCode,
address onBehalfOf
) external;
/**
* @notice Repays a borrowed `amount` on a specific reserve, burning the equivalent debt tokens owned
* - E.g. User repays 100 USDC, burning 100 variable/stable debt tokens of the `onBehalfOf` address
* @param asset The address of the borrowed underlying asset previously borrowed
* @param amount The amount to repay
* - Send the value type(uint256).max in order to repay the whole debt for `asset` on the specific `debtMode`
* @param rateMode The interest rate mode at of the debt the user wants to repay: 1 for Stable, 2 for Variable
* @param onBehalfOf Address of the user who will get his debt reduced/removed. Should be the address of the
* user calling the function if he wants to reduce/remove his own debt, or the address of any other
* other borrower whose debt should be removed
**/
function repay(
address asset,
uint256 amount,
uint256 rateMode,
address onBehalfOf
) external;
/**
* @dev Allows a borrower to swap his debt between stable and variable mode, or viceversa
* @param asset The address of the underlying asset borrowed
* @param rateMode The rate mode that the user wants to swap to
**/
function swapBorrowRateMode(address asset, uint256 rateMode) external;
/**
* @dev Rebalances the stable interest rate of a user to the current stable rate defined on the reserve.
* - Users can be rebalanced if the following conditions are satisfied:
* 1. Usage ratio is above 95%
* 2. the current deposit APY is below REBALANCE_UP_THRESHOLD * maxVariableBorrowRate, which means that too much has been
* borrowed at a stable rate and depositors are not earning enough
* @param asset The address of the underlying asset borrowed
* @param user The address of the user to be rebalanced
**/
function rebalanceStableBorrowRate(address asset, address user) external;
/**
* @dev Allows depositors to enable/disable a specific deposited asset as collateral
* @param asset The address of the underlying asset deposited
* @param useAsCollateral `true` if the user wants to use the deposit as collateral, `false` otherwise
**/
function setUserUseReserveAsCollateral(address asset, bool useAsCollateral) external;
/**
* @dev Function to liquidate a non-healthy position collateral-wise, with Health Factor below 1
* - The caller (liquidator) covers `debtToCover` amount of debt of the user getting liquidated, and receives
* a proportionally amount of the `collateralAsset` plus a bonus to cover market risk
* @param collateralAsset The address of the underlying asset used as collateral, to receive as result of the liquidation
* @param debtAsset The address of the underlying borrowed asset to be repaid with the liquidation
* @param user The address of the borrower getting liquidated
* @param debtToCover The debt amount of borrowed `asset` the liquidator wants to cover
* @param receiveAToken `true` if the liquidators wants to receive the collateral aTokens, `false` if he wants
* to receive the underlying collateral asset directly
**/
function liquidationCall(
address collateralAsset,
address debtAsset,
address user,
uint256 debtToCover,
bool receiveAToken
) external;
/**
* @dev Allows smartcontracts to access the liquidity of the pool within one transaction,
* as long as the amount taken plus a fee is returned.
* IMPORTANT There are security concerns for developers of flashloan receiver contracts that must be kept into consideration.
* For further details please visit https://developers.aave.com
* @param receiverAddress The address of the contract receiving the funds, implementing the IFlashLoanReceiver interface
* @param assets The addresses of the assets being flash-borrowed
* @param amounts The amounts amounts being flash-borrowed
* @param modes Types of the debt to open if the flash loan is not returned:
* 0 -> Don't open any debt, just revert if funds can't be transferred from the receiver
* 1 -> Open debt at stable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
* 2 -> Open debt at variable rate for the value of the amount flash-borrowed to the `onBehalfOf` address
* @param onBehalfOf The address that will receive the debt in the case of using on `modes` 1 or 2
* @param params Variadic packed params to pass to the receiver as extra information
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata modes,
address onBehalfOf,
bytes calldata params,
uint16 referralCode
) external;
/**
* @dev Returns the user account data across all the reserves
* @param user The address of the user
* @return totalCollateralETH the total collateral in ETH of the user
* @return totalDebtETH the total debt in ETH of the user
* @return availableBorrowsETH the borrowing power left of the user
* @return currentLiquidationThreshold the liquidation threshold of the user
* @return ltv the loan to value of the user
* @return healthFactor the current health factor of the user
**/
function getUserAccountData(address user)
external
view
returns (
uint256 totalCollateralETH,
uint256 totalDebtETH,
uint256 availableBorrowsETH,
uint256 currentLiquidationThreshold,
uint256 ltv,
uint256 healthFactor
);
function initReserve(
address reserve,
address aTokenAddress,
address stableDebtAddress,
address variableDebtAddress,
address interestRateStrategyAddress
) external;
function setReserveInterestRateStrategyAddress(address reserve, address rateStrategyAddress)
external;
function setConfiguration(address reserve, uint256 configuration) external;
/**
* @dev Returns the configuration of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The configuration of the reserve
**/
function getConfiguration(address asset) external view returns (DataTypes.ReserveConfigurationMap memory);
/**
* @dev Returns the configuration of the user across all the reserves
* @param user The user address
* @return The configuration of the user
**/
function getUserConfiguration(address user) external view returns (DataTypes.UserConfigurationMap memory);
/**
* @dev Returns the normalized income normalized income of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The reserve's normalized income
*/
function getReserveNormalizedIncome(address asset) external view returns (uint256);
/**
* @dev Returns the normalized variable debt per unit of asset
* @param asset The address of the underlying asset of the reserve
* @return The reserve normalized variable debt
*/
function getReserveNormalizedVariableDebt(address asset) external view returns (uint256);
/**
* @dev Returns the state and configuration of the reserve
* @param asset The address of the underlying asset of the reserve
* @return The state of the reserve
**/
function getReserveData(address asset) external view returns (DataTypes.ReserveData memory);
function finalizeTransfer(
address asset,
address from,
address to,
uint256 amount,
uint256 balanceFromAfter,
uint256 balanceToBefore
) external;
function getReservesList() external view returns (address[] memory);
function getAddressesProvider() external view returns (ILendingPoolAddressesProviderV2);
function setPause(bool val) external;
function paused() external view returns (bool);
}
interface IFlashLoans {
function flashLoan(
address recipient,
address[] memory tokens,
uint256[] memory amounts,
bytes memory userData
) external;
}
interface IERC3156FlashBorrower {
/**
* @dev Receive a flash loan.
* @param initiator The initiator of the loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param fee The additional amount of tokens to repay.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
* @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}
interface IERC3156FlashLender {
/**
* @dev The amount of currency available to be lent.
* @param token The loan currency.
* @return The amount of `token` that can be borrowed.
*/
function maxFlashLoan(
address token
) external view returns (uint256);
/**
* @dev The fee to be charged for a given loan.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @return The amount of `token` to be charged for the loan, on top of the returned principal.
*/
function flashFee(
address token,
uint256 amount
) external view returns (uint256);
/**
* @dev Initiate a flash loan.
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
* @param token The loan currency.
* @param amount The amount of tokens lent.
* @param data Arbitrary data structure, intended to contain user-defined parameters.
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}
abstract contract IFlashLoanBase{
struct FlashLoanParams {
address[] tokens;
uint256[] amounts;
uint256[] modes;
address onBehalfOf;
address flParamGetterAddr;
bytes flParamGetterData;
bytes recipeData;
}
}
type Id is bytes32;
struct MarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv;
}
struct MorphoBluePosition {
uint256 supplyShares;
uint128 borrowShares;
uint128 collateral;
}
struct Market {
uint128 totalSupplyAssets;
uint128 totalSupplyShares;
uint128 totalBorrowAssets;
uint128 totalBorrowShares;
uint128 lastUpdate;
uint128 fee;
}
struct Authorization {
address authorizer;
address authorized;
bool isAuthorized;
uint256 nonce;
uint256 deadline;
}
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
interface IMorphoBase {
/// @notice The EIP-712 domain separator.
/// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing
/// the same chain id because the domain separator would be the same.
function DOMAIN_SEPARATOR() external view returns (bytes32);
/// @notice The owner of the contract.
/// @dev It has the power to change the owner.
/// @dev It has the power to set fees on markets and set the fee recipient.
/// @dev It has the power to enable but not disable IRMs and LLTVs.
function owner() external view returns (address);
/// @notice The fee recipient of all markets.
/// @dev The recipient receives the fees of a given market through a supply position on that market.
function feeRecipient() external view returns (address);
/// @notice Whether the `irm` is enabled.
function isIrmEnabled(address irm) external view returns (bool);
/// @notice Whether the `lltv` is enabled.
function isLltvEnabled(uint256 lltv) external view returns (bool);
/// @notice Whether `authorized` is authorized to modify `authorizer`'s positions.
/// @dev Anyone is authorized to modify their own positions, regardless of this variable.
function isAuthorized(address authorizer, address authorized) external view returns (bool);
/// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures.
function nonce(address authorizer) external view returns (uint256);
/// @notice Sets `newOwner` as `owner` of the contract.
/// @dev Warning: No two-step transfer ownership.
/// @dev Warning: The owner can be set to the zero address.
function setOwner(address newOwner) external;
/// @notice Enables `irm` as a possible IRM for market creation.
/// @dev Warning: It is not possible to disable an IRM.
function enableIrm(address irm) external;
/// @notice Enables `lltv` as a possible LLTV for market creation.
/// @dev Warning: It is not possible to disable a LLTV.
function enableLltv(uint256 lltv) external;
/// @notice Sets the `newFee` for the given market `marketParams`.
/// @dev Warning: The recipient can be the zero address.
function setFee(MarketParams memory marketParams, uint256 newFee) external;
/// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee.
/// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost.
/// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To
/// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes.
function setFeeRecipient(address newFeeRecipient) external;
/// @notice Creates the market `marketParams`.
/// @dev Here is the list of assumptions on the market's dependencies (tokens, IRM and oracle) that guarantees
/// Morpho behaves as expected:
/// - The token should be ERC-20 compliant, except that it can omit return values on `transfer` and `transferFrom`.
/// - The token balance of Morpho should only decrease on `transfer` and `transferFrom`. In particular, tokens with
/// burn functions are not supported.
/// - The token should not re-enter Morpho on `transfer` nor `transferFrom`.
/// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount
/// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported.
/// - The IRM should not re-enter Morpho.
/// - The oracle should return a price with the correct scaling.
/// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties
/// (funds could get stuck):
/// - The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue.
/// - A very high amount of assets (~1e35) supplied or borrowed can make the computation of `toSharesUp` and
/// `toSharesDown` overflow.
/// - The IRM can revert on `borrowRate`.
/// - A very high borrow rate returned by the IRM can make the computation of `interest` in `_accrueInterest`
/// overflow.
/// - The oracle can revert on `price`. Note that this can be used to prevent `borrow`, `withdrawCollateral` and
/// `liquidate` from being used under certain market conditions.
/// - A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or
/// the computation of `assetsRepaid` in `liquidate` overflow.
/// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to
/// the point where `totalBorrowShares` is very large and borrowing overflows.
function createMarket(MarketParams memory marketParams) external;
/// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoSupply` function with the given `data`.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller
/// is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific amount
/// of shares is given for full compatibility and precision.
/// @dev If the supply of a market gets depleted, the supply share price instantly resets to
/// `VIRTUAL_ASSETS`:`VIRTUAL_SHARES`.
/// @dev Supplying a large amount can revert for overflow.
/// @param marketParams The market to supply assets to.
/// @param assets The amount of assets to supply.
/// @param shares The amount of shares to mint.
/// @param onBehalf The address that will own the increased supply position.
/// @param data Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed.
/// @return assetsSupplied The amount of assets supplied.
/// @return sharesSupplied The amount of shares minted.
function supply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256 assetsSupplied, uint256 sharesSupplied);
/// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` to `receiver`.
/// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow.
/// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to
/// conversion roundings between shares and assets.
/// @param marketParams The market to withdraw assets from.
/// @param assets The amount of assets to withdraw.
/// @param shares The amount of shares to burn.
/// @param onBehalf The address of the owner of the supply position.
/// @param receiver The address that will receive the withdrawn assets.
/// @return assetsWithdrawn The amount of assets withdrawn.
/// @return sharesWithdrawn The amount of shares burned.
function withdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn);
/// @notice Borrows `assets` or `shares` on behalf of `onBehalf` to `receiver`.
/// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller
/// is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is given for
/// full compatibility and precision.
/// @dev If the borrow of a market gets depleted, the borrow share price instantly resets to
/// `VIRTUAL_ASSETS`:`VIRTUAL_SHARES`.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Borrowing a large amount can revert for overflow.
/// @param marketParams The market to borrow assets from.
/// @param assets The amount of assets to borrow.
/// @param shares The amount of shares to mint.
/// @param onBehalf The address that will own the increased borrow position.
/// @param receiver The address that will receive the borrowed assets.
/// @return assetsBorrowed The amount of assets borrowed.
/// @return sharesBorrowed The amount of shares minted.
function borrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed);
/// @notice Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoReplay` function with the given `data`.
/// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`.
/// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow.
/// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion
/// roundings between shares and assets.
/// @param marketParams The market to repay assets to.
/// @param assets The amount of assets to repay.
/// @param shares The amount of shares to burn.
/// @param onBehalf The address of the owner of the debt position.
/// @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed.
/// @return assetsRepaid The amount of assets repaid.
/// @return sharesRepaid The amount of shares burned.
function repay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256 assetsRepaid, uint256 sharesRepaid);
/// @notice Supplies `assets` of collateral on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoSupplyCollateral` function with the given `data`.
/// @dev Interest are not accrued since it's not required and it saves gas.
/// @dev Supplying a large amount can revert for overflow.
/// @param marketParams The market to supply collateral to.
/// @param assets The amount of collateral to supply.
/// @param onBehalf The address that will own the increased collateral position.
/// @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed.
function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data)
external;
/// @notice Withdraws `assets` of collateral on behalf of `onBehalf` to `receiver`.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Withdrawing an amount corresponding to more collateral than supplied will revert for underflow.
/// @param marketParams The market to withdraw collateral from.
/// @param assets The amount of collateral to withdraw.
/// @param onBehalf The address of the owner of the collateral position.
/// @param receiver The address that will receive the collateral assets.
function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
external;
/// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the
/// given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's
/// `onMorphoLiquidate` function with the given `data`.
/// @dev Either `seizedAssets` or `repaidShares` should be zero.
/// @dev Seizing more than the collateral balance will underflow and revert without any error message.
/// @dev Repaying more than the borrow balance will underflow and revert without any error message.
/// @param marketParams The market of the position.
/// @param borrower The owner of the position.
/// @param seizedAssets The amount of collateral to seize.
/// @param repaidShares The amount of shares to repay.
/// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed.
/// @return The amount of assets seized.
/// @return The amount of assets repaid.
function liquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytes memory data
) external returns (uint256, uint256);
/// @notice Executes a flash loan.
/// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all
/// markets combined, plus donations).
/// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached:
/// - `flashFee` is zero.
/// - `maxFlashLoan` is the token's balance of this contract.
/// - The receiver of `assets` is the caller.
/// @param token The token to flash loan.
/// @param assets The amount of assets to flash loan.
/// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback.
function flashLoan(address token, uint256 assets, bytes calldata data) external;
/// @notice Sets the authorization for `authorized` to manage `msg.sender`'s positions.
/// @param authorized The authorized address.
/// @param newIsAuthorized The new authorization status.
function setAuthorization(address authorized, bool newIsAuthorized) external;
/// @notice Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions.
/// @dev Warning: Reverts if the signature has already been submitted.
/// @dev The signature is malleable, but it has no impact on the security here.
/// @dev The nonce is passed as argument to be able to revert with a different error message.
/// @param authorization The `Authorization` struct.
/// @param signature The signature.
function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external;
/// @notice Accrues interest for the given market `marketParams`.
function accrueInterest(MarketParams memory marketParams) external;
/// @notice Returns the data stored on the different `slots`.
function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
}
interface IMorphoStaticTyping is IMorphoBase {
/// @notice The state of the position of `user` on the market corresponding to `id`.
/// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
/// accrual.
function position(Id id, address user)
external
view
returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral);
/// @notice The state of the market corresponding to `id`.
/// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest
/// accrual.
function market(Id id)
external
view
returns (
uint128 totalSupplyAssets,
uint128 totalSupplyShares,
uint128 totalBorrowAssets,
uint128 totalBorrowShares,
uint128 lastUpdate,
uint128 fee
);
/// @notice The market params corresponding to `id`.
/// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
/// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
function idToMarketParams(Id id)
external
view
returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv);
}
interface IMorphoBlue is IMorphoBase {
/// @notice The state of the position of `user` on the market corresponding to `id`.
/// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest
/// accrual.
function position(Id id, address user) external view returns (MorphoBluePosition memory p);
/// @notice The state of the market corresponding to `id`.
/// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last
/// interest accrual.
function market(Id id) external view returns (Market memory m);
/// @notice The market params corresponding to `id`.
/// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
/// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
function idToMarketParams(Id id) external view returns (MarketParams memory);
}
abstract contract IUniswapV3Factory {
function getPool(address token0, address token1, uint24 fee) external virtual view returns (address poolAddress);
}
abstract contract IUniswapV3Pool {
struct Slot0 {
// the current price
uint160 sqrtPriceX96;
// the current tick
int24 tick;
// the most-recently updated index of the observations array
uint16 observationIndex;
// the current maximum number of observations that are being stored
uint16 observationCardinality;
// the next maximum number of observations to store, triggered in observations.write
uint16 observationCardinalityNext;
// the current protocol fee as a percentage of the swap fee taken on withdrawal
// represented as an integer denominator (1/x)%
uint8 feeProtocol;
// whether the pool is locked
bool unlocked;
}
function slot0() external virtual view returns (Slot0 memory);
function fee() external virtual view returns (uint24 fee);
function flash(address recipient, uint256 amount0, uint256 amount1, bytes memory data) external virtual;
}
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
error ReentrantCall();
constructor () {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and make it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
if (_status == _ENTERED){
revert ReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}
abstract contract IWETH {
function allowance(address, address) public virtual view returns (uint256);
function balanceOf(address) public virtual view returns (uint256);
function approve(address, uint256) public virtual;
function transfer(address, uint256) public virtual returns (bool);
function transferFrom(
address,
address,
uint256
) public virtual returns (bool);
function deposit() public payable virtual;
function withdraw(uint256) public virtual;
}
library TokenUtils {
using SafeERC20 for IERC20;
address public constant WETH_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address public constant ETH_ADDR = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @dev Only approves the amount if allowance is lower than amount, does not decrease allowance
function approveToken(
address _tokenAddr,
address _to,
uint256 _amount
) internal {
if (_tokenAddr == ETH_ADDR) return;
if (IERC20(_tokenAddr).allowance(address(this), _to) < _amount) {
IERC20(_tokenAddr).safeApprove(_to, _amount);
}
}
function pullTokensIfNeeded(
address _token,
address _from,
uint256 _amount
) internal returns (uint256) {
// handle max uint amount
if (_amount == type(uint256).max) {
_amount = getBalance(_token, _from);
}
if (_from != address(0) && _from != address(this) && _token != ETH_ADDR && _amount != 0) {
IERC20(_token).safeTransferFrom(_from, address(this), _amount);
}
return _amount;
}
function withdrawTokens(
address _token,
address _to,
uint256 _amount
) internal returns (uint256) {
if (_amount == type(uint256).max) {
_amount = getBalance(_token, address(this));
}
if (_to != address(0) && _to != address(this) && _amount != 0) {
if (_token != ETH_ADDR) {
IERC20(_token).safeTransfer(_to, _amount);
} else {
(bool success, ) = _to.call{value: _amount}("");
require(success, "Eth send fail");
}
}
return _amount;
}
function depositWeth(uint256 _amount) internal {
IWETH(WETH_ADDR).deposit{value: _amount}();
}
function withdrawWeth(uint256 _amount) internal {
IWETH(WETH_ADDR).withdraw(_amount);
}
function getBalance(address _tokenAddr, address _acc) internal view returns (uint256) {
if (_tokenAddr == ETH_ADDR) {
return _acc.balance;
} else {
return IERC20(_tokenAddr).balanceOf(_acc);
}
}
function getTokenDecimals(address _token) internal view returns (uint256) {
if (_token == ETH_ADDR) return 18;
return IERC20(_token).decimals();
}
}
contract FLAction is ActionBase, ReentrancyGuard, IFlashLoanBase, FLHelper {
using TokenUtils for address;
/// @dev FL Initiator must be this contract
error UntrustedInitiator();
/// @dev Caller in these functions must be relevant FL source address
error UntrustedLender();
// Wrong FL payback amount sent
error WrongPaybackAmountError();
// When FL source is not found
error NonexistentFLSource();
enum FLSource {
EMPTY,
AAVEV2,
BALANCER,
GHO,
MAKER,
AAVEV3,
UNIV3,
SPARK,
MORPHO_BLUE,
CURVEUSD
}
/// @inheritdoc ActionBase
function actionType() public pure override returns (uint8) {
return uint8(ActionType.FL_ACTION);
}
// solhint-disable-next-line no-empty-blocks
function executeActionDirect(bytes memory _callData) public payable override {}
/// @inheritdoc ActionBase
/// @notice This action doesn't use flParamGetterAddr and flParamGetterData
/// @notice flParamGetterData is used to choose between FL providers
function executeAction(
bytes memory _callData,
bytes32[] memory,
uint8[] memory,
bytes32[] memory
) public payable override returns (bytes32) {
FlashLoanParams memory params = abi.decode(_callData, (FlashLoanParams));
FLSource flSource = FLSource(uint8(bytes1(params.flParamGetterData)));
handleFlashloan(params, flSource);
return bytes32(params.amounts[0]);
}
function handleFlashloan(FlashLoanParams memory _flParams, FLSource _source) internal {
if (_source == FLSource.AAVEV2) {
_flAaveV2(_flParams);
} else if (_source == FLSource.BALANCER) {
_flBalancer(_flParams);
} else if (_source == FLSource.GHO) {
_flGho(_flParams);
} else if (_source == FLSource.MAKER) {
_flMaker(_flParams);
} else if (_source == FLSource.AAVEV3) {
_flAaveV3(_flParams);
} else if (_source == FLSource.UNIV3) {
_flUniV3(_flParams);
} else if (_source == FLSource.SPARK) {
_flSpark(_flParams);
} else if (_source == FLSource.MORPHO_BLUE) {
_flMorphoBlue(_flParams);
} else if (_source == FLSource.CURVEUSD) {
_flCurveUSD(_flParams);
} else {
revert NonexistentFLSource();
}
}
/// @notice Gets a Fl from Aave and returns back the execution to the action address
/// @param _flParams All the amounts/tokens and related aave fl data
function _flAaveV2(FlashLoanParams memory _flParams) internal {
ILendingPoolV2(AAVE_LENDING_POOL).flashLoan(
address(this),
_flParams.tokens,
_flParams.amounts,
_flParams.modes,
_flParams.onBehalfOf,
_flParams.recipeData,
AAVE_REFERRAL_CODE
);
emit ActionEvent(
"FLAction",
abi.encode(
"AAVEV2",
_flParams.tokens,
_flParams.amounts,
_flParams.modes,
_flParams.onBehalfOf
)
);
}
/// @notice Gets a Fl from Aave V3 and returns back the execution to the action address
/// @param _flParams All the amounts/tokens and related aave fl data
function _flAaveV3(FlashLoanParams memory _flParams) internal {
ILendingPoolV2(AAVE_V3_LENDING_POOL).flashLoan(
address(this),
_flParams.tokens,
_flParams.amounts,
_flParams.modes,
_flParams.onBehalfOf,
_flParams.recipeData,
AAVE_REFERRAL_CODE
);
emit ActionEvent(
"FLAction",
abi.encode(
"AAVEV3",
_flParams.tokens,
_flParams.amounts,
_flParams.modes,
_flParams.onBehalfOf
)
);
}
/// @notice Gets a FL from Balancer and returns back the execution to the action address
function _flBalancer(FlashLoanParams memory _flParams) internal {
IFlashLoans(VAULT_ADDR).flashLoan(
address(this),
_flParams.tokens,
_flParams.amounts,
_flParams.recipeData
);
emit ActionEvent("FLAction", abi.encode("BALANCER", _flParams));
}
/// @notice Gets a GHO FL from Gho Flash Minter
function _flGho(FlashLoanParams memory _flParams) internal {
IERC3156FlashLender(GHO_FLASH_MINTER_ADDR).flashLoan(
IERC3156FlashBorrower(address(this)),
GHO_ADDR,
_flParams.amounts[0],
_flParams.recipeData
);
emit ActionEvent("FLAction", abi.encode("GHO", _flParams.amounts[0]));
}
/// @notice Gets a DAI flash loan from Maker and returns back the execution to the action address
/// @param _flParams All the amounts/tokens and related aave fl data
function _flMaker(FlashLoanParams memory _flParams) internal {
IERC3156FlashLender(DSS_FLASH_ADDR).flashLoan(
IERC3156FlashBorrower(address(this)),
DAI_ADDR,
_flParams.amounts[0],
_flParams.recipeData
);
emit ActionEvent("FLAction", abi.encode("MAKER", _flParams.amounts[0]));
}
function _flUniV3(FlashLoanParams memory _flParams) internal {
// modes aren't used so we set them to later know starting balances
_flParams.modes = new uint256[](2);
_flParams.modes[0] = _flParams.amounts[0] > 0 ? _flParams.tokens[0].getBalance(address(this)) : 0;
_flParams.modes[1] = _flParams.amounts[1] > 0 ? _flParams.tokens[1].getBalance(address(this)) : 0;
/// @dev FlashLoanParams.tokens, first two array indexes contain tokens, third index contains pool address
IUniswapV3Pool(_flParams.tokens[2]).flash(
address(this),
_flParams.amounts[0],
_flParams.amounts[1],
abi.encode(_flParams)
);
emit ActionEvent("FLAction", abi.encode("UNIV3", _flParams.amounts[0]));
}
/// @notice Gets a Fl from Spark and returns back the execution to the action address
function _flSpark(FlashLoanParams memory _flParams) internal {
ILendingPoolV2(SPARK_LENDING_POOL).flashLoan(
address(this),
_flParams.tokens,
_flParams.amounts,
_flParams.modes,
_flParams.onBehalfOf,
_flParams.recipeData,
SPARK_REFERRAL_CODE
);
emit ActionEvent("FLAction", abi.encode("SPARK", _flParams.amounts[0]));
}
/// @notice Gets a FL from Morpho blue and returns back the execution to the action address
function _flMorphoBlue(FlashLoanParams memory _params) internal {
IMorphoBlue(MORPHO_BLUE_ADDR).flashLoan(
_params.tokens[0],
_params.amounts[0],
abi.encode(_params.recipeData, _params.tokens[0])
);
emit ActionEvent("FLAction", abi.encode("MORPHOBLUE", _params.amounts[0]));
}
function _flCurveUSD(FlashLoanParams memory _params) internal {
IERC3156FlashLender(CURVEUSD_FLASH_ADDR).flashLoan(
IERC3156FlashBorrower(address(this)),
CURVEUSD_ADDR,
_params.amounts[0],
_params.recipeData
);
emit ActionEvent("FLAction", abi.encode("CURVEUSD", _params.amounts[0]));
}
/// @notice Aave callback function that formats and calls back RecipeExecutor
/// FLSource == AAVE | SPARK
function executeOperation(
address[] memory _assets,
uint256[] memory _amounts,
uint256[] memory _fees,
address _initiator,
bytes memory _params
) public nonReentrant returns (bool) {
if (msg.sender != AAVE_LENDING_POOL && msg.sender != AAVE_V3_LENDING_POOL && msg.sender != SPARK_LENDING_POOL) {
revert UntrustedLender();
}
if (_initiator != address(this)) {
revert UntrustedInitiator();
}
(Recipe memory currRecipe, address wallet) = abi.decode(_params, (Recipe, address));
uint256[] memory balancesBefore = new uint256[](_assets.length);
// Send FL amounts to user wallet
for (uint256 i = 0; i < _assets.length; ++i) {
_assets[i].withdrawTokens(wallet, _amounts[i]);
balancesBefore[i] = _assets[i].getBalance(address(this));
}
_executeRecipe(wallet, isDSProxy(wallet), currRecipe, _amounts[0] + _fees[0]);
// return FL
for (uint256 i = 0; i < _assets.length; i++) {
uint256 paybackAmount = _amounts[i] + _fees[i];
bool correctAmount = _assets[i].getBalance(address(this)) ==
paybackAmount + balancesBefore[i];
if (_assets[i] == ST_ETH_ADDR && !correctAmount) {
flFeeFaucet.my2Wei(ST_ETH_ADDR);
correctAmount = true;
}
if (!correctAmount) {
revert WrongPaybackAmountError();
}
_assets[i].approveToken(address(msg.sender), paybackAmount);
}
return true;
}
/// @notice Balancer FL callback function that formats and calls back RecipeExecutor
/// FLSource == BALANCER
function receiveFlashLoan(
address[] memory _tokens,
uint256[] memory _amounts,
uint256[] memory _feeAmounts,
bytes memory _userData
) external nonReentrant {
if (msg.sender != VAULT_ADDR) {
revert UntrustedLender();
}
(Recipe memory currRecipe, address wallet) = abi.decode(_userData, (Recipe, address));
uint256[] memory balancesBefore = new uint256[](_tokens.length);
for (uint256 i = 0; i < _tokens.length; i++) {
_tokens[i].withdrawTokens(wallet, _amounts[i]);
balancesBefore[i] = _tokens[i].getBalance(address(this));
}
_executeRecipe(wallet, isDSProxy(wallet), currRecipe, _amounts[0] + _feeAmounts[0]);
for (uint256 i = 0; i < _tokens.length; i++) {
uint256 paybackAmount = _amounts[i] + (_feeAmounts[i]);
if (_tokens[i].getBalance(address(this)) != paybackAmount + balancesBefore[i]) {
revert WrongPaybackAmountError();
}
_tokens[i].withdrawTokens(address(VAULT_ADDR), paybackAmount);
}
}
/// @notice ERC3156 callback function that formats and calls back RecipeExecutor
/// FLSource == MAKER | GHO | CURVEUSD
function onFlashLoan(
address _initiator,
address _token,
uint256 _amount,
uint256 _fee,
bytes calldata _data
) external nonReentrant returns (bytes32) {
if (msg.sender != DSS_FLASH_ADDR && msg.sender != GHO_FLASH_MINTER_ADDR && msg.sender != CURVEUSD_FLASH_ADDR) {
revert UntrustedLender();
}
if (_initiator != address(this)) {
revert UntrustedInitiator();
}
(Recipe memory currRecipe, address wallet) = abi.decode(_data, (Recipe, address));
_token.withdrawTokens(wallet, _amount);
uint256 balanceBefore = _token.getBalance(address(this));
uint256 paybackAmount = _amount +_fee;
_executeRecipe(wallet, isDSProxy(wallet), currRecipe, paybackAmount);
if (_token.getBalance(address(this)) != paybackAmount + balanceBefore) {
revert WrongPaybackAmountError();
}
if (msg.sender == CURVEUSD_FLASH_ADDR) {
_token.withdrawTokens(msg.sender, paybackAmount);
} else {
_token.approveToken(msg.sender, paybackAmount);
}
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
function uniswapV3FlashCallback(
uint256 _fee0,
uint256 _fee1,
bytes memory _params
) external nonReentrant {
FlashLoanParams memory params = abi.decode(_params, (FlashLoanParams));
{
uint24 fee = IUniswapV3Pool(msg.sender).fee();
address realPool = IUniswapV3Factory(UNI_V3_FACTORY).getPool(params.tokens[0], params.tokens[1], uint24(fee));
if (msg.sender != realPool) revert UntrustedLender();
}
(Recipe memory currRecipe, address wallet) = abi.decode(params.recipeData, (Recipe, address));
params.tokens[0].withdrawTokens(wallet, params.amounts[0]);
params.tokens[1].withdrawTokens(wallet, params.amounts[1]);
_executeRecipe(wallet, isDSProxy(wallet), currRecipe, params.amounts[0]);
uint256 expectedBalance0 = params.modes[0] + params.amounts[0] + _fee0;
uint256 expectedBalance1 = params.modes[1] + params.amounts[1] + _fee1;
uint256 currBalance0 = params.amounts[0] > 0 ? params.tokens[0].getBalance(address(this)) : 0;
uint256 currBalance1 = params.amounts[1] > 0 ? params.tokens[1].getBalance(address(this)) : 0;
bool isCorrectAmount0 = currBalance0 == expectedBalance0;
bool isCorrectAmount1 = currBalance1 == expectedBalance1;
if (params.amounts[0] > 0 && params.tokens[0] == ST_ETH_ADDR && !isCorrectAmount0) {
flFeeFaucet.my2Wei(ST_ETH_ADDR);
isCorrectAmount0 = true;
}
if (params.amounts[1] > 0 && params.tokens[1] == ST_ETH_ADDR && !isCorrectAmount1) {
flFeeFaucet.my2Wei(ST_ETH_ADDR);
isCorrectAmount1 = true;
}
if (!isCorrectAmount0) revert WrongPaybackAmountError();
if (!isCorrectAmount1) revert WrongPaybackAmountError();
params.tokens[0].withdrawTokens(msg.sender, params.amounts[0] + _fee0);
params.tokens[1].withdrawTokens(msg.sender, params.amounts[1] + _fee1);
}
function onMorphoFlashLoan(uint256 assets, bytes calldata data) external nonReentrant{
if (msg.sender != MORPHO_BLUE_ADDR) {
revert UntrustedLender();
}
(bytes memory taskData, address token) = abi.decode(data, (bytes, address));
(Recipe memory currRecipe, address wallet) = abi.decode(taskData, (Recipe, address));
token.withdrawTokens(wallet, assets);
uint256 balanceBefore = token.getBalance(address(this));
_executeRecipe(wallet, isDSProxy(wallet), currRecipe, assets);
if (token.getBalance(address(this)) != assets + balanceBefore) {
revert WrongPaybackAmountError();
}
token.approveToken(MORPHO_BLUE_ADDR, assets);
}
}