Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/strategies/LendingStrategy.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { Sickle } from "contracts/Sickle.sol";
import { SickleFactory } from "contracts/SickleFactory.sol";
import { ConnectorRegistry } from "contracts/ConnectorRegistry.sol";
import { FlashloanStrategy } from "contracts/strategies/FlashloanStrategy.sol";
import { FlashloanInitiator } from
"contracts/strategies/lending/FlashloanInitiator.sol";
import { TransferLib } from "contracts/libraries/TransferLib.sol";
import { ZapLib, ZapIn, ZapOut } from "contracts/libraries/ZapLib.sol";
import { FeesLib } from "contracts/libraries/FeesLib.sol";
import { IFarmConnector, Farm } from "contracts/interfaces/IFarmConnector.sol";
import { LendingStrategyFees } from
"contracts/strategies/lending/LendingStructs.sol";
import { StrategyModule } from "contracts/modules/StrategyModule.sol";
contract LendingStrategy is FlashloanInitiator, StrategyModule {
error SwapPathNotSupported(); // 0x6b46d10f
error InputArgumentsMismatch(); // 0xe3814450
struct Libraries {
TransferLib transferLib;
ZapLib zapLib;
FeesLib feesLib;
}
struct DepositParams {
address tokenIn;
uint256 amountIn;
ZapIn zap;
}
struct WithdrawParams {
address tokenOut;
ZapOut zap;
}
struct CompoundParams {
Farm farm;
bytes extraData;
ZapIn zap;
}
TransferLib public immutable transferLib;
ZapLib public immutable zapLib;
FeesLib public immutable feesLib;
address public immutable strategyAddress;
constructor(
SickleFactory factory,
ConnectorRegistry connectorRegistry,
FlashloanStrategy flashloanStrategy,
Libraries memory libraries
)
FlashloanInitiator(flashloanStrategy)
StrategyModule(factory, connectorRegistry)
{
transferLib = libraries.transferLib;
zapLib = libraries.zapLib;
feesLib = libraries.feesLib;
strategyAddress = address(this);
}
/// FLASHLOAN FUNCTIONS ///
/// @notice Deposit using a zap
/// Deposit asset X, swap for asset A
/// Flashloan asset A, or flashloan asset Z and swap for asset A
/// Supply asset A, borrow asset A + fees, repay flashloan
function deposit(
DepositParams calldata depositParams,
IncreaseParams calldata increaseParams,
FlashloanParams calldata flashloanParams,
address[] memory sweepTokens,
address approved,
bytes32 referralCode
) public payable flashloanParamCheck(flashloanParams) {
Sickle sickle = getOrDeploySickle(msg.sender, approved, referralCode);
address[] memory targets = new address[](1);
bytes[] memory data = new bytes[](1);
targets[0] = address(transferLib);
data[0] = abi.encodeCall(
TransferLib.transferTokenFromUser,
(
depositParams.tokenIn,
depositParams.amountIn,
strategyAddress,
LendingStrategyFees.Deposit
)
);
sickle.multicall{ value: msg.value }(targets, data);
targets = new address[](1);
data = new bytes[](1);
targets[0] = address(zapLib);
data[0] = abi.encodeCall(ZapLib.zapIn, (depositParams.zap));
sickle.multicall(targets, data);
flashloan_deposit(address(sickle), increaseParams, flashloanParams);
if (sweepTokens.length > 0) {
targets = new address[](1);
data = new bytes[](1);
targets[0] = address(transferLib);
data[0] =
abi.encodeCall(TransferLib.transferTokensToUser, (sweepTokens));
sickle.multicall(targets, data);
}
}
/// @notice Repay asset A loan with flashloan, withdraw collateral asset A
function withdraw(
DecreaseParams calldata decreaseParams,
FlashloanParams calldata flashloanParams,
WithdrawParams calldata withdrawParams,
address[] memory sweepTokens
) public {
Sickle sickle = getSickle(msg.sender);
flashloan_withdraw(address(sickle), decreaseParams, flashloanParams);
address[] memory targets = new address[](3);
bytes[] memory data = new bytes[](3);
targets[0] = address(zapLib);
data[0] = abi.encodeCall(ZapLib.zapOut, (withdrawParams.zap));
targets[1] = address(feesLib);
data[1] = abi.encodeCall(
FeesLib.chargeFee,
(
strategyAddress,
LendingStrategyFees.Withdraw,
withdrawParams.tokenOut,
0
)
);
targets[2] = address(transferLib);
data[2] =
abi.encodeCall(TransferLib.transferTokensToUser, (sweepTokens));
sickle.multicall(targets, data);
}
/// @notice Claim accrued rewards, sell for loan token and leverage with
/// flashloan
function compound(
CompoundParams calldata compoundParams,
IncreaseParams calldata increaseParams,
FlashloanParams calldata flashloanParams,
address[] memory sweepTokens
) public {
Sickle sickle = getSickle(msg.sender);
// delegatecall callback function
address[] memory targets = new address[](3);
bytes[] memory data = new bytes[](3);
targets[0] =
connectorRegistry.connectorOf(compoundParams.farm.stakingContract);
data[0] = abi.encodeCall(
IFarmConnector.claim,
(compoundParams.farm, compoundParams.extraData)
);
targets[1] = address(zapLib);
data[1] = abi.encodeCall(ZapLib.zapIn, (compoundParams.zap));
address flashloanToken = flashloanParams.flashloanAssets[0]
== address(0)
? flashloanParams.flashloanAssets[1]
: flashloanParams.flashloanAssets[0];
targets[2] = address(feesLib);
data[2] = abi.encodeCall(
FeesLib.chargeFee,
(strategyAddress, LendingStrategyFees.Compound, flashloanToken, 0)
);
sickle.multicall(targets, data);
flashloan_deposit(address(sickle), increaseParams, flashloanParams);
if (sweepTokens.length > 0) {
targets = new address[](1);
data = new bytes[](1);
targets[0] = address(transferLib);
data[0] =
abi.encodeCall(TransferLib.transferTokensToUser, (sweepTokens));
sickle.multicall(targets, data);
}
}
}
"
},
"contracts/Sickle.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { SickleStorage } from "contracts/base/SickleStorage.sol";
import { Multicall } from "contracts/base/Multicall.sol";
import { SickleRegistry } from "contracts/SickleRegistry.sol";
/// @title Sickle contract
/// @author vfat.tools
/// @notice Sickle facilitates farming and interactions with Masterchef
/// contracts
/// @dev Base contract inheriting from all the other "manager" contracts
contract Sickle is SickleStorage, Multicall {
/// @notice Function to receive ETH
receive() external payable { }
/// @param sickleRegistry_ Address of the SickleRegistry contract
constructor(
SickleRegistry sickleRegistry_
) Multicall(sickleRegistry_) {
_disableInitializers();
}
/// @param sickleOwner_ Address of the Sickle owner
function initialize(
address sickleOwner_,
address approved_
) external initializer {
SickleStorage._initializeSickleStorage(sickleOwner_, approved_);
}
/// INTERNALS ///
function onERC721Received(
address, // operator
address, // from
uint256, // tokenId
bytes calldata // data
) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
function onERC1155Received(
address, // operator
address, // from
uint256, // id
uint256, // value
bytes calldata // data
) external pure returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address, // operator
address, // from
uint256[] calldata, // ids
uint256[] calldata, // values
bytes calldata // data
) external pure returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
}
"
},
"contracts/SickleFactory.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol";
import { Sickle } from "contracts/Sickle.sol";
import { SickleRegistry } from "contracts/SickleRegistry.sol";
import { Admin } from "contracts/base/Admin.sol";
/// @title SickleFactory contract
/// @author vfat.tools
/// @notice Factory deploying new Sickle contracts
contract SickleFactory is Admin {
/// EVENTS ///
/// @notice Emitted when a new Sickle contract is deployed
/// @param admin Address receiving the admin rights of the Sickle contract
/// @param sickle Address of the newly deployed Sickle contract
event Deploy(address indexed admin, address sickle);
/// @notice Thrown when the caller is not whitelisted
/// @param caller Address of the non-whitelisted caller
error CallerNotWhitelisted(address caller); // 0x252c8273
/// @notice Thrown when the factory is not active and a deploy is attempted
error NotActive(); // 0x80cb55e2
/// @notice Thrown when a Sickle contract is already deployed for a user
error SickleAlreadyDeployed(); //0xf6782ef1
/// STORAGE ///
mapping(address => address) private _sickles;
mapping(address => address) private _admins;
mapping(address => bytes32) public _referralCodes;
/// @notice Address of the SickleRegistry contract
SickleRegistry public immutable registry;
/// @notice Address of the Sickle implementation contract
address public immutable implementation;
/// @notice Address of the previous SickleFactory contract (if applicable)
SickleFactory public immutable previousFactory;
/// @notice Whether the factory is active (can deploy new Sickle contracts)
bool public isActive = true;
/// WRITE FUNCTIONS ///
/// @param admin_ Address of the admin
/// @param sickleRegistry_ Address of the SickleRegistry contract
/// @param sickleImplementation_ Address of the Sickle implementation
/// contract
/// @param previousFactory_ Address of the previous SickleFactory contract
/// if applicable
constructor(
address admin_,
address sickleRegistry_,
address sickleImplementation_,
address previousFactory_
) Admin(admin_) {
registry = SickleRegistry(sickleRegistry_);
implementation = sickleImplementation_;
previousFactory = SickleFactory(previousFactory_);
}
function setActive(
bool active
) external onlyAdmin {
isActive = active;
}
function _deploy(
address admin,
address approved,
bytes32 referralCode
) internal returns (address sickle) {
sickle = Clones.cloneDeterministic(
implementation, keccak256(abi.encode(admin))
);
Sickle(payable(sickle)).initialize(admin, approved);
_sickles[admin] = sickle;
_admins[sickle] = admin;
if (referralCode != bytes32(0)) {
_referralCodes[sickle] = referralCode;
}
emit Deploy(admin, sickle);
}
function _getSickle(
address admin
) internal returns (address sickle) {
sickle = _sickles[admin];
if (sickle != address(0)) {
return sickle;
}
if (address(previousFactory) != address(0)) {
sickle = previousFactory.sickles(admin);
if (sickle != address(0)) {
_sickles[admin] = sickle;
_admins[sickle] = admin;
_referralCodes[sickle] = previousFactory.referralCodes(sickle);
return sickle;
}
}
}
/// @notice Predict the address of a Sickle contract for a specific user
/// @param admin Address receiving the admin rights of the Sickle contract
/// @return sickle Address of the predicted Sickle contract
function predict(
address admin
) external view returns (address) {
bytes32 salt = keccak256(abi.encode(admin));
return Clones.predictDeterministicAddress(implementation, salt);
}
/// @notice Returns the Sickle contract for a specific user
/// @param admin Address that owns the Sickle contract
/// @return sickle Address of the Sickle contract
function sickles(
address admin
) external view returns (address sickle) {
sickle = _sickles[admin];
if (sickle == address(0) && address(previousFactory) != address(0)) {
sickle = previousFactory.sickles(admin);
}
}
/// @notice Returns the admin for a specific Sickle contract
/// @param sickle Address of the Sickle contract
/// @return admin Address that owns the Sickle contract
function admins(
address sickle
) external view returns (address admin) {
admin = _admins[sickle];
if (admin == address(0) && address(previousFactory) != address(0)) {
admin = previousFactory.admins(sickle);
}
}
/// @notice Returns the referral code for a specific Sickle contract
/// @param sickle Address of the Sickle contract
/// @return referralCode Referral code for the user
function referralCodes(
address sickle
) external view returns (bytes32 referralCode) {
referralCode = _referralCodes[sickle];
if (
referralCode == bytes32(0) && address(previousFactory) != address(0)
) {
referralCode = previousFactory.referralCodes(sickle);
}
}
/// @notice Deploys a new Sickle contract for a specific user, or returns
/// the existing one if it exists
/// @param admin Address receiving the admin rights of the Sickle contract
/// @param approved Address approved to manage automation
/// @param referralCode Referral code for the user
/// @return sickle Address of the deployed Sickle contract
function getOrDeploy(
address admin,
address approved,
bytes32 referralCode
) external returns (address sickle) {
if (!isActive) {
revert NotActive();
}
if (!registry.isWhitelistedCaller(msg.sender)) {
revert CallerNotWhitelisted(msg.sender);
}
if ((sickle = _getSickle(admin)) != address(0)) {
return sickle;
}
return _deploy(admin, approved, referralCode);
}
/// @notice Deploys a new Sickle contract for a specific user
/// @dev Sickle contracts are deployed with create2, the address of the
/// admin is used as a salt, so all the Sickle addresses can be pre-computed
/// and only 1 Sickle will exist per address
/// @param approved Address approved to manage automation
/// @param referralCode Referral code for the user
/// @return sickle Address of the deployed Sickle contract
function deploy(
address approved,
bytes32 referralCode
) external returns (address sickle) {
if (!isActive) {
revert NotActive();
}
if (_getSickle(msg.sender) != address(0)) {
revert SickleAlreadyDeployed();
}
return _deploy(msg.sender, approved, referralCode);
}
}
"
},
"contracts/ConnectorRegistry.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { Admin } from "contracts/base/Admin.sol";
import { TimelockAdmin } from "contracts/base/TimelockAdmin.sol";
error ConnectorNotRegistered(address target);
error CustomRegistryAlreadyRegistered();
interface ICustomConnectorRegistry {
function connectorOf(
address target
) external view returns (address);
}
contract ConnectorRegistry is Admin, TimelockAdmin {
event ConnectorChanged(address target, address connector);
event CustomRegistryAdded(address registry);
event CustomRegistryRemoved(address registry);
error ConnectorAlreadySet(address target);
error ConnectorNotSet(address target);
error ArrayLengthMismatch();
ICustomConnectorRegistry[] public customRegistries;
mapping(address target => address connector) private connectors_;
constructor(
address admin_,
address timelockAdmin_
) Admin(admin_) TimelockAdmin(timelockAdmin_) { }
/// Admin functions
/// @notice Update connector addresses for a batch of targets.
/// @dev Controls which connector contracts are used for the specified
/// targets.
/// @custom:access Restricted to protocol admin.
function setConnectors(
address[] calldata targets,
address[] calldata connectors
) external onlyAdmin {
if (targets.length != connectors.length) {
revert ArrayLengthMismatch();
}
for (uint256 i; i != targets.length;) {
if (connectors_[targets[i]] != address(0)) {
revert ConnectorAlreadySet(targets[i]);
}
connectors_[targets[i]] = connectors[i];
emit ConnectorChanged(targets[i], connectors[i]);
unchecked {
++i;
}
}
}
function updateConnectors(
address[] calldata targets,
address[] calldata connectors
) external onlyTimelockAdmin {
if (targets.length != connectors.length) {
revert ArrayLengthMismatch();
}
for (uint256 i; i != targets.length;) {
if (connectors_[targets[i]] == address(0)) {
revert ConnectorNotSet(targets[i]);
}
connectors_[targets[i]] = connectors[i];
emit ConnectorChanged(targets[i], connectors[i]);
unchecked {
++i;
}
}
}
/// @notice Append an address to the custom registries list.
/// @custom:access Restricted to protocol admin.
function addCustomRegistry(
ICustomConnectorRegistry registry
) external onlyAdmin {
if (isCustomRegistry(registry)) {
revert CustomRegistryAlreadyRegistered();
}
customRegistries.push(registry);
emit CustomRegistryAdded(address(registry));
}
/// @notice Replace an address in the custom registries list.
/// @custom:access Restricted to protocol admin.
function updateCustomRegistry(
uint256 index,
ICustomConnectorRegistry newRegistry
) external onlyTimelockAdmin {
ICustomConnectorRegistry oldRegistry = customRegistries[index];
emit CustomRegistryRemoved(address(oldRegistry));
customRegistries[index] = newRegistry;
if (address(newRegistry) != address(0)) {
emit CustomRegistryAdded(address(newRegistry));
}
}
/// Public functions
function connectorOf(
address target
) external view returns (address) {
address connector = _getConnector(target);
if (connector != address(0)) {
return connector;
}
revert ConnectorNotRegistered(target);
}
function hasConnector(
address target
) external view returns (bool) {
return _getConnector(target) != address(0);
}
function isCustomRegistry(
ICustomConnectorRegistry registry
) public view returns (bool) {
for (uint256 i; i != customRegistries.length;) {
if (address(customRegistries[i]) == address(registry)) {
return true;
}
unchecked {
++i;
}
}
return false;
}
/// Internal functions
function _getConnector(
address target
) internal view returns (address) {
address connector = connectors_[target];
if (connector != address(0)) {
return connector;
}
uint256 length = customRegistries.length;
for (uint256 i; i != length;) {
if (address(customRegistries[i]) != address(0)) {
(bool success, bytes memory data) = address(customRegistries[i])
.staticcall(
abi.encodeWithSelector(
ICustomConnectorRegistry.connectorOf.selector, target
)
);
if (success && data.length == 32) {
address _connector = abi.decode(data, (address));
if (_connector != address(0)) {
return _connector;
}
}
}
unchecked {
++i;
}
}
return address(0);
}
}
"
},
"contracts/strategies/FlashloanStrategy.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IMorpho } from "@morpho-blue/interfaces/IMorpho.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { BPS_BASIS } from "contracts/base/Constants.sol";
import { Admin } from "contracts/base/Admin.sol";
import { ILendingPoolV2 } from
"contracts/interfaces/external/flashloans/ILendingPoolV2.sol";
import { IPoolV3 } from "contracts/interfaces/external/flashloans/IPoolV3.sol";
import {
IBalancerVault,
IFlashLoanRecipient
} from "contracts/interfaces/external/flashloans/IBalancerVault.sol";
import { IUniswapV3Pool } from
"contracts/interfaces/external/uniswap/IUniswapV3Pool.sol";
import { IUniswapV2Factory } from
"contracts/interfaces/external/uniswap/IUniswapV2Factory.sol";
import { IUniswapV2Pair } from
"contracts/interfaces/external/uniswap/IUniswapV2Pair.sol";
import { IUniswapV3Factory } from
"contracts/interfaces/external/uniswap/IUniswapV3Factory.sol";
import { SickleRegistry } from "contracts/SickleRegistry.sol";
import { SickleFactory } from "contracts/SickleFactory.sol";
import { Multicall } from "contracts/base/Multicall.sol";
library FlashloanStrategyEvents {
event SelectorLinked(bytes4 selector, address strategy);
}
/// @title FlashloanStrategy contract
/// @author vfat.tools
/// @notice Manages approved flashloan providers for the sickle and calls to and
/// from flashloan providers
contract FlashloanStrategy is Admin, IFlashLoanRecipient {
/// ENUMS ///
enum FlashloanStatus {
FLASHLOAN_UNLOCKED,
FLASHLOAN_INITIATED,
CALLBACK_INITIATED
}
enum FlashloanProvider {
AAVEV2,
AAVEV3,
BALANCER,
UNIV2,
UNIV3,
MORPHO
}
/// ERRORS: Strategy ///
error NotFlashloanStrategy(); // 0x9862416d
error InvalidFlashloanData();
error FlashloanNotInitiated();
error FlashloanAlreadyInitiated();
error FlashloanNotReset();
error NotAnAssetPair();
error InvalidAssetOrder();
error UnauthorizedOperation();
error SenderIsNotStrategy();
error SenderIsNotAaveLendingPool();
error SenderIsNotAaveV3LendingPool();
error SenderIsNotBalancerVault();
error SenderIsNotUniswapPool();
error SenderIsNorMorpho();
error NotSingleAsset();
/// ERRORS: Registry ///
/// @notice Thrown when array lengths don't match when registering flashloan
/// operation types
error ParamsMismatch();
/// @notice Thrown when trying to override stub for an already registered
/// flashloan operation
error SelectorAlreadyLinked();
/// STORAGE ///
// Contract addresses
SickleFactory public immutable sickleFactory;
address public immutable aaveV2LendingPool;
address public immutable aaveV3LendingPool;
address public immutable balancerVault;
address public immutable quickswapFactoryAddr;
address public immutable uniswapV3FactoryAddr;
address public immutable morpho;
// Operational variables
bytes32 flashloanDataHash;
FlashloanStatus currentFlashloanStatus; // added reentrancy protection
/// @notice Returns the stub contract address corresponding to the flashloan
/// operation's function selector
/// @dev if address(0) is returned, the flashloan operation does not exist
/// or is not whitelisted
/// param: flashloanOpSelector Function selector of the flashloan operation
/// @return Address of the corresponding stub if it exists
mapping(bytes4 => address) public whitelistedFlashloanOpsRegistry;
/// WRITE FUNCTIONS ///
/// @param admin_ Address of the admin
/// @param whitelistedOpsSelectors_ Array of flashloan operation types to
/// whitelist
/// @param correspondingStrategies_ Array of strategy addresses where
/// flashloan operations will be executed
constructor(
address admin_,
SickleFactory sickleFactory_,
address aaveV2LendingPool_,
address aaveV3LendingPool_,
address balancerVault_,
address quickswapFactoryAddr_,
address uniswapV3FactoryAddr_,
address morpho_,
bytes4[] memory whitelistedOpsSelectors_,
address[] memory correspondingStrategies_
) Admin(admin_) {
sickleFactory = sickleFactory_;
aaveV2LendingPool = aaveV2LendingPool_;
aaveV3LendingPool = aaveV3LendingPool_;
balancerVault = balancerVault_;
quickswapFactoryAddr = quickswapFactoryAddr_;
uniswapV3FactoryAddr = uniswapV3FactoryAddr_;
morpho = morpho_;
currentFlashloanStatus = FlashloanStatus.FLASHLOAN_UNLOCKED;
_setWhitelistedFlashloanOpsSelectors(
whitelistedOpsSelectors_, correspondingStrategies_
);
}
/// @notice Sets the approval status and corresponding stubs of several
/// flashloan operation types
/// @param whitelistedOpsSelectors Array of flashloan operation types to
/// whitelist
/// @param correspondingStrategies Array of strategy addresses where
/// flashloan operations will be executed
/// @custom:access Restricted to protocol admin.
function setWhitelistedFlashloanOpsSelectors(
bytes4[] memory whitelistedOpsSelectors,
address[] memory correspondingStrategies
) external onlyAdmin {
_setWhitelistedFlashloanOpsSelectors(
whitelistedOpsSelectors, correspondingStrategies
);
}
function _setWhitelistedFlashloanOpsSelectors(
bytes4[] memory whitelistedOpsSelectors,
address[] memory correspondingStrategies
) internal {
if (whitelistedOpsSelectors.length != correspondingStrategies.length) {
revert ParamsMismatch();
}
uint256 length = whitelistedOpsSelectors.length;
for (uint256 i; i < length;) {
if (
whitelistedFlashloanOpsRegistry[whitelistedOpsSelectors[i]]
!= address(0)
) {
revert SelectorAlreadyLinked();
}
whitelistedFlashloanOpsRegistry[whitelistedOpsSelectors[i]] =
correspondingStrategies[i];
emit FlashloanStrategyEvents.SelectorLinked(
whitelistedOpsSelectors[i], correspondingStrategies[i]
);
unchecked {
++i;
}
}
}
modifier callbackSafetyCheck(
bytes memory params
) {
bytes32 hashCheck = keccak256(params);
if (hashCheck != flashloanDataHash) {
revert InvalidFlashloanData();
}
if (currentFlashloanStatus != FlashloanStatus.FLASHLOAN_INITIATED) {
revert FlashloanNotInitiated();
}
_;
// resetting currentFlashloanStatus to FLASHLOAN_UNLOCKED after all
// operations are finished
currentFlashloanStatus = FlashloanStatus.FLASHLOAN_UNLOCKED;
}
/// @notice Routing function that initiates the flashloan at the indicated
/// provider
/// @param flashloanProvider Bytes blob containing the name of the flashloan
/// provider and optionally the fee tier
/// @param assets Array of contract addresses of the tokens being borrowed
/// @param amounts Array of amounts of tokens being borrowed
/// @param params Bytes blob contains all necessary parameters for the
/// post-flashloan function
/// @dev if using a uniswap call, the assets array must contain the token0
/// and token1 of the called pool in the correct order
function initiateFlashloan(
address sickleAddress,
bytes calldata flashloanProvider,
address[] calldata assets,
uint256[] calldata amounts,
bytes calldata params
) external {
if (assets.length != amounts.length) {
revert SickleRegistry.ArrayLengthMismatch();
}
if (currentFlashloanStatus != FlashloanStatus.FLASHLOAN_UNLOCKED) {
revert FlashloanAlreadyInitiated();
}
SickleRegistry registry = sickleFactory.registry();
if (registry.isWhitelistedCaller(msg.sender) == false) {
revert SenderIsNotStrategy();
}
// setting the currentFlashloanStatus to FLASHLOAN_INITIATED to avoid
// reentrancy and allow callbacks only
currentFlashloanStatus = FlashloanStatus.FLASHLOAN_INITIATED;
(FlashloanProvider providerType, uint24 providerFee) =
abi.decode(flashloanProvider, (FlashloanProvider, uint24));
if (providerType == FlashloanProvider.AAVEV2) {
uint256[] memory modes = new uint256[](assets.length);
// mode 0 = no debt incurred, everything repaid at end of flashloan
bytes memory aaveV2params = abi.encode(sickleAddress, params);
// storing the hash of the callback data for safety checks
flashloanDataHash = keccak256(aaveV2params);
ILendingPoolV2(aaveV2LendingPool).flashLoan(
address(this), // flashloan receiver
assets,
amounts,
modes,
address(0), // onBehalfOf variable
aaveV2params,
0 // referral code
);
} else if (providerType == FlashloanProvider.AAVEV3) {
bytes memory aaveV3params = abi.encode(sickleAddress, params);
// storing the hash of the callback data for safety checks
flashloanDataHash = keccak256(aaveV3params);
if (assets.length == 1) {
IPoolV3(aaveV3LendingPool).flashLoanSimple(
address(this), // flashloan receiver
assets[0],
amounts[0],
aaveV3params,
0 // referral code
);
} else {
uint256[] memory modes = new uint256[](assets.length);
// mode 0 = no debt incurred, everything repaid at end of
// flashloan
IPoolV3(aaveV3LendingPool).flashLoan(
address(this), // flashloan receiver
assets,
amounts,
modes,
address(0),
aaveV3params,
0 // referral code
);
}
} else if (providerType == FlashloanProvider.BALANCER) {
bytes memory balancerParams = abi.encode(sickleAddress, params);
// storing the hash of the callback data for safety checks
flashloanDataHash = keccak256(balancerParams);
IBalancerVault(balancerVault).flashLoan(
this, assets, amounts, balancerParams
);
} else if (providerType == FlashloanProvider.UNIV2) {
if (assets.length != 2) revert NotAnAssetPair();
if (assets[0] > assets[1]) revert InvalidAssetOrder();
address poolAddress = IUniswapV2Factory(quickswapFactoryAddr)
.getPair(assets[0], assets[1]);
(,, uint256[] memory premiums,) =
abi.decode(params[4:], (address[], uint256[], uint256[], bytes));
bytes memory uniswapFlashParams = abi.encode(
sickleAddress, poolAddress, assets, amounts, premiums, params
);
// storing the hash of the callback data for safety checks
flashloanDataHash = keccak256(uniswapFlashParams);
IUniswapV2Pair(poolAddress).swap(
amounts[0], amounts[1], address(this), uniswapFlashParams
);
} else if (providerType == FlashloanProvider.UNIV3) {
if (assets.length != 2) revert NotAnAssetPair();
if (assets[0] > assets[1]) revert InvalidAssetOrder();
address poolAddress = IUniswapV3Factory(uniswapV3FactoryAddr)
.getPool(assets[0], assets[1], providerFee);
(,, uint256[] memory premiums,) =
abi.decode(params[4:], (address[], uint256[], uint256[], bytes));
bytes memory uniswapFlashParams = abi.encode(
sickleAddress, poolAddress, assets, amounts, premiums, params
);
// storing the hash of the callback data for safety checks
flashloanDataHash = keccak256(uniswapFlashParams);
IUniswapV3Pool(poolAddress).flash(
address(this), amounts[0], amounts[1], uniswapFlashParams
);
} else {
if (assets.length != 1) revert NotSingleAsset();
bytes memory morphoParams =
abi.encode(sickleAddress, assets[0], params);
// storing the hash of the callback data for safety checks
flashloanDataHash = keccak256(morphoParams);
IMorpho(morpho).flashLoan(assets[0], amounts[0], morphoParams);
}
// resetting the flashloanDataHash variable to zero bytes at the end of
// conversion operation
flashloanDataHash = bytes32(0);
if (currentFlashloanStatus != FlashloanStatus.FLASHLOAN_UNLOCKED) {
revert FlashloanNotReset();
}
}
/// @notice Callback function for flashloans coming from Aave's LendingPool
/// contract (single-asset V3)
/// @param asset Contract address of the token being borrowed
/// @param amount Amount of tokens being borrowed
/// @param premium Premium amount charged by the Aave protocol
/// @param paramsInput Bytes blob contains all necessary parameters for the
/// post-flashloan function
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address, // initiator
bytes calldata paramsInput
) external callbackSafetyCheck(paramsInput) returns (bool) {
if (msg.sender != aaveV3LendingPool) {
revert SenderIsNotAaveV3LendingPool();
}
(address sickleAddress, bytes memory callback) =
abi.decode(paramsInput, (address, bytes));
address targetStrategy = _checkSelector(callback);
// setting the currentFlashloanStatus to CALLBACK_INITIATED to avoid
// reentrancy
currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;
// transfer borrowed assets to the sickle
SafeTransferLib.safeTransfer(asset, sickleAddress, amount);
// execute post-flashloan strategy function via a delegatecall from the
// sickle
{
address[] memory targetStrategyArray = new address[](1);
targetStrategyArray[0] = targetStrategy;
bytes[] memory calldataArray = new bytes[](1);
calldataArray[0] = callback;
Multicall(sickleAddress).multicall(
targetStrategyArray, calldataArray
);
}
// approving borrowed funds + premiums to be repaid to the Lending Pool
// contract
SafeTransferLib.safeApprove(asset, aaveV3LendingPool, amount + premium);
return true;
// debt is automatically repaid
}
function executeOperation(
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
address, // initiator
bytes memory paramsInput
) external callbackSafetyCheck(paramsInput) returns (bool) {
if (assets.length != amounts.length || assets.length != premiums.length)
{
revert SickleRegistry.ArrayLengthMismatch();
}
if (msg.sender != aaveV2LendingPool && msg.sender != aaveV3LendingPool)
{
revert SenderIsNotAaveLendingPool();
}
(address sickleAddress, bytes memory callback) =
abi.decode(paramsInput, (address, bytes));
address targetStrategy = _checkSelector(callback);
// setting the currentFlashloanStatus to CALLBACK_INITIATED to avoid
// reentrancy
currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;
// transfer borrowed assets to the sickle
_transferAssets(sickleAddress, assets, amounts);
// execute post-flashloan strategy function via a delegatecall from the
// sickle
{
address[] memory targetStrategyArray = new address[](1);
targetStrategyArray[0] = targetStrategy;
bytes[] memory calldataArray = new bytes[](1);
calldataArray[0] = callback;
Multicall(sickleAddress).multicall(
targetStrategyArray, calldataArray
);
}
// approving borrowed funds + premiums to be repaid to the Lending Pool
// contract
_approveAssets(msg.sender, assets, amounts, premiums);
return true;
// debt is automatically repaid
}
/// @notice Callback function for flashloans coming from Balancer's Vault
/// contract
/// @param tokens Array of contract addresses of the tokens being borrowed
/// @param amounts Array of amounts of tokens being borrowed
/// @param premiums Array of premium amounts charged by the Balancer
/// protocol
/// @param paramsInput Bytes blob contains all necessary parameters for the
/// post-flashloan function
function receiveFlashLoan(
IERC20[] calldata tokens,
uint256[] calldata amounts,
uint256[] calldata premiums,
bytes memory paramsInput
) external callbackSafetyCheck(paramsInput) {
if (tokens.length != amounts.length || tokens.length != premiums.length)
{
revert SickleRegistry.ArrayLengthMismatch();
}
if (msg.sender != balancerVault) {
revert SenderIsNotBalancerVault();
}
(address sickleAddress, bytes memory params) =
abi.decode(paramsInput, (address, bytes));
address targetStrategy = _checkSelector(params);
// setting the currentFlashloanStatus to CALLBACK_INITIATED to avoid
// reentrancy
currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;
// convert data and transfer borrowed assets to the sickle
_transferAssets(sickleAddress, tokens, amounts);
// execute post-flashloan strategy function via a delegatecall from the
// sickle
{
address[] memory targetStrategyArray = new address[](1);
targetStrategyArray[0] = targetStrategy;
bytes[] memory calldataArray = new bytes[](1);
calldataArray[0] = params;
Multicall(sickleAddress).multicall(
targetStrategyArray, calldataArray
);
}
// transferring borrowed funds + premiums to be repaid to the Balancer
// Vault contract
_transferAssets(balancerVault, tokens, amounts, premiums);
}
/// @notice Callback function for flashloans coming from UniswapV2 pairs
/// @param params Bytes blob contains all necessary parameters for the
/// post-flashloan function
function uniswapV2Call(
address, //sender
uint256, // amount0
uint256, // amount1
bytes calldata params
) external callbackSafetyCheck(params) {
(
address sickleAddress,
address poolAddress,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
bytes memory callback
) = abi.decode(
params, (address, address, address[], uint256[], uint256[], bytes)
);
_uniswapCallback(
sickleAddress, poolAddress, assets, amounts, premiums, callback
);
}
/// @notice Callback function for flashloans coming from UniswapV3 pools
/// @param params Bytes blob contains all necessary parameters for the
/// post-flashloan function
function uniswapV3FlashCallback(
uint256, // fee0
uint256, // fee1
bytes calldata params
) external callbackSafetyCheck(params) {
(
address sickleAddress,
address poolAddress,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
bytes memory callback
) = abi.decode(
params, (address, address, address[], uint256[], uint256[], bytes)
);
_uniswapCallback(
sickleAddress, poolAddress, assets, amounts, premiums, callback
);
}
/// @notice Callback function for flashloans coming from PancakeV3 pools
/// @param params Bytes blob contains all necessary parameters for the
/// post-flashloan function
function pancakeV3FlashCallback(
uint256, // fee0
uint256, // fee1
bytes calldata params
) external callbackSafetyCheck(params) {
(
address sickleAddress,
address poolAddress,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
bytes memory callback
) = abi.decode(
params, (address, address, address[], uint256[], uint256[], bytes)
);
_uniswapCallback(
sickleAddress, poolAddress, assets, amounts, premiums, callback
);
}
function onMorphoFlashLoan(
uint256 assets,
bytes calldata data
) external callbackSafetyCheck(data) {
(address sickleAddress, address token, bytes memory callback) =
abi.decode(data, (address, address, bytes));
// Check that we're being called from Morpho
if (msg.sender != morpho) {
revert SenderIsNorMorpho();
}
address targetStrategy = _checkSelector(callback);
// Set the currentFlashloanStatus to CALLBACK_INITIATED to avoid
// reentrancy
currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;
// Transfer borrowed assets to the sickle
SafeTransferLib.safeTransfer(token, sickleAddress, assets);
// Execute post-flashloan strategy function via a delegatecall from the
// sickle
{
address[] memory targetStrategyArray = new address[](1);
targetStrategyArray[0] = targetStrategy;
bytes[] memory calldataArray = new bytes[](1);
calldataArray[0] = callback;
Multicall(sickleAddress).multicall(
targetStrategyArray, calldataArray
);
}
SafeTransferLib.safeApprove(token, morpho, assets);
}
/// VIEW FUNCTIONS ///
/// @notice Returns the premium amounts on borrowed amounts based on the
/// flashloan provider
/// @param flashloanProvider Bytes blob indicating the selected flashloan
/// provider and optionally the fee tier
/// @param amounts Array of amounts of tokens being borrowed
/// @return Array of premium amounts corresponding to each borrowed amount
function calculatePremiums(
bytes calldata flashloanProvider,
uint256[] calldata amounts
) public view returns (uint256[] memory) {
uint256[] memory premiums = new uint256[](amounts.length);
(FlashloanProvider providerType, uint24 providerFee) =
abi.decode(flashloanProvider, (FlashloanProvider, uint24));
if (providerType == FlashloanProvider.AAVEV2) {
uint256 aaveV2FlashloanPremiumInBasisPoints =
ILendingPoolV2(aaveV2LendingPool).FLASHLOAN_PREMIUM_TOTAL();
uint256 length = amounts.length;
for (uint256 i; i < length;) {
if (amounts[i] > 0 && aaveV2FlashloanPremiumInBasisPoints > 0) {
premiums[i] = // Rounding down
(
(amounts[i] * aaveV2FlashloanPremiumInBasisPoints)
/ BPS_BASIS
);
}
unchecked {
++i;
}
}
} else if (providerType == FlashloanProvider.AAVEV3) {
uint256 aaveV3FlashloanPremiumInBasisPoints =
IPoolV3(aaveV3LendingPool).FLASHLOAN_PREMIUM_TOTAL();
uint256 length = amounts.length;
for (uint256 i; i < length;) {
if (amounts[i] > 0 && aaveV3FlashloanPremiumInBasisPoints > 0) {
premiums[i] = // Rounding to nearest neighbor
(
(
(
amounts[i] * aaveV3FlashloanPremiumInBasisPoints
+ (BPS_BASIS / 2)
) / BPS_BASIS
)
);
}
unchecked {
++i;
}
}
} else if (providerType == FlashloanProvider.BALANCER) {
uint256 balancerFlashLoanFeePercentage = IBalancerVault(
balancerVault
).getProtocolFeesCollector().getFlashLoanFeePercentage();
uint256 length = amounts.length;
for (uint256 i; i < length;) {
// reproducing the mulUp() function from Balancer's FixedPoint
// helper library
if (balancerFlashLoanFeePercentage != 0 && amounts[i] != 0) {
premiums[i] = (
(amounts[i] * balancerFlashLoanFeePercentage - 1) / 1e18
) + 1;
}
unchecked {
++i;
}
}
} else if (providerType == FlashloanProvider.UNIV2) {
uint256 length = amounts.length;
for (uint256 i; i < length;) {
if (amounts[i] > 0) {
premiums[i] = ((amounts[i] * 3 - 1) / 997) + 1;
}
unchecked {
++i;
}
}
} else if (providerType == FlashloanProvider.UNIV3) {
uint256 length = amounts.length;
for (uint256 i; i < length;) {
if (amounts[i] > 0 && providerFee > 0) {
premiums[i] =
((amounts[i] * providerFee - 1) / BPS_BASIS / 100) + 1;
// hundredths of basis points
}
unchecked {
++i;
}
}
}
return premiums;
}
/// @notice Returns the first four bytes of a given bytes blob
/// @param params Bytes blob from which we want to extract the first four
/// bytes corresponding to the function selector
/// @return selector Bytes4 containing the function selector
/// @dev helper function for uniswapV2Call and uniswapV3Call functions where
/// the function selector is not at the beginning of the bytes parameter in
/// the callback
function extractSelector(
bytes memory params
) public pure returns (bytes4 selector) {
selector = bytes4(params);
}
/// INTERNALS ///
function _transferAssets(
address to,
address[] memory assets,
uint256[] memory amounts
) internal {
uint256[] memory premiums = new uint256[](assets.length);
_transferAssets(to, assets, amounts, premiums);
}
function _transferAssets(
address to,
IERC20[] memory assets,
uint256[] memory amounts
) internal {
uint256[] memory premiums = new uint256[](assets.length);
_transferAssets(to, assets, amounts, premiums);
}
function _transferAssets(
address to,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums
) internal {
uint256 length = assets.length;
for (uint256 i; i < length;) {
uint256 total = amounts[i] + premiums[i];
if (total > 0) {
SafeTransferLib.safeTransfer(assets[i], to, total);
}
unchecked {
++i;
}
}
}
function _transferAssets(
address to,
IERC20[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums
) internal {
uint256 length = assets.length;
for (uint256 i; i < length;) {
uint256 total = amounts[i] + premiums[i];
if (total > 0) {
SafeTransferLib.safeTransfer(address(assets[i]), to, total);
}
unchecked {
++i;
}
}
}
function _approveAssets(
address to,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums
) internal {
uint256 length = assets.length;
for (uint256 i; i < length;) {
uint256 total = amounts[i] + premiums[i];
if (total > 0) {
SafeTransferLib.safeApprove(assets[i], to, total);
}
unchecked {
++i;
}
}
}
function _checkSelector(
bytes memory params
) internal view returns (address) {
// extracting function selector from the callback parameters
bytes4 flashloanOpSelector = extractSelector(params);
// fetching the corresponding strategy from the registry
address targetStrategy =
whitelistedFlashloanOpsRegistry[flashloanOpSelector];
if (targetStrategy == address(0)) {
revert UnauthorizedOperation();
}
return targetStrategy;
}
function _uniswapCallback(
address sickleAddress,
address poolAddress,
address[] memory assets,
uint256[] memory amounts,
uint256[] memory premiums,
bytes memory callback
) internal {
if (assets.length != amounts.length || assets.length != premiums.length)
{
revert SickleRegistry.ArrayLengthMismatch();
}
// Check that we're being called from a Uniswap pool
if (msg.sender != poolAddress) {
revert SenderIsNotUniswapPool();
}
address targetStrategy = _checkSelector(callback);
// Set the currentFlashloanStatus to CALLBACK_INITIATED to avoid
// reentrancy
currentFlashloanStatus = FlashloanStatus.CALLBACK_INITIATED;
// Transfer borrowed assets to the sickle
_transferAssets(sickleAddress, assets, amounts);
// Execute post-flashloan strategy function via a delegatecall from the
// sickle
{
address[] memory targetStrategyArray = new address[](1);
targetStrategyArray[0] = targetStrategy;
bytes[] memory calldataArray = new bytes[](1);
calldataArray[0] = callback;
Multicall(sickleAddress).multicall(
targetStrategyArray, calldataArray
);
}
// Transfer borrowed funds + premiums to be repaid to the Uniswap pool
// contract
_transferAssets(poolAddress, assets, amounts, premiums);
}
}
"
},
"contracts/strategies/lending/FlashloanInitiator.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { LendingStructs } from "contracts/strategies/lending/LendingStructs.sol";
import { IFlashloanCallback } from "contracts/interfaces/IFlashloanCallback.sol";
import { FlashloanStrategy } from "contracts/strategies/FlashloanStrategy.sol";
abstract contract FlashloanInitiator is LendingStructs {
error ArrayLengthMismatch();
modifier flashloanParamCheck(
FlashloanParams calldata flashloanParams
) {
if (
flashloanParams.flashloanAssets.length
!= flashloanParams.flashloanAmounts.length
) {
revert ArrayLengthMismatch();
}
_;
}
FlashloanStrategy public immutable flashloanStrategy;
constructor(
FlashloanStrategy flashloanStrategy_
) {
flashloanStrategy = flashloanStrategy_;
}
/// Internal functions ///
function flashloan_deposit(
address sickleAddress,
IncreaseParams calldata increaseParams,
FlashloanParams calldata flashloanParams
) internal {
uint256[] memory premiums = flashloanStrategy.calculatePremiums(
flashloanParams.flashloanProvider, flashloanParams.flashloanAmounts
);
bytes memory callback = abi.encodeCall(
IFlashloanCallback.flashloanDepositCallback,
(
flashloanParams.flashloanAssets,
flashloanParams.flashloanAmounts,
premiums,
abi.encode(increaseParams)
)
);
flashloanStrategy.initiateFlashloan(
sickleAddress,
flashloanParams.flashloanProvider,
flashloanParams.flashloanAssets,
flashloanParams.flashloanAmounts,
callback
);
}
function flashloan_withdraw(
address sickleAddress,
DecreaseParams calldata decreaseParams,
FlashloanParams calldata flashLoanParams
) internal {
// calculate expected premiums on flashloan amount
uint256[] memory premiums = flashloanStrategy.calculatePremiums(
flashLoanParams.flashloanProvider, flashLoanParams.flashloanAmounts
);
bytes memory encoded = abi.encodeCall(
IFlashloanCallback.flashloanWithdrawCallback,
(
flashLoanParams.flashloanAssets,
flashLoanParams.flashloanAmounts,
premiums,
abi.encode(decreaseParams)
)
);
flashloanStrategy.initiateFlashloan(
sickleAddress,
flashLoanParams.flashloanProvider,
flashLoanParams.flashloanAssets,
flashLoanParams.flashloanAmounts,
encoded
);
}
}
"
},
"contracts/libraries/TransferLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { MsgValueModule } from "contracts/modules/MsgValueModule.sol";
import { WETH } from "lib/solmate/src/tokens/WETH.sol";
import { Sickle } from "contracts/Sickle.sol";
import { SafeTransferLib } from "lib/solmate/src/utils/SafeTransferLib.sol";
import { IERC20 } from
"lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import { IFeesLib } from "contracts/interfaces/libraries/IFeesLib.sol";
import { DelegateModule } from "contracts/modules/DelegateModule.sol";
import { ITransferLib } from "contracts/interfaces/libraries/ITransferLib.sol";
contract TransferLib is MsgValueModule, DelegateModule, ITransferLib {
address public constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public constant UNISWAP_ETH =
0x0000000000000000000000000000000000000000;
WETH public immutable weth;
IFeesLib public immutable feesLib;
constructor(IFeesLib feesLib_, WETH weth_) {
feesLib = feesLib_;
weth = weth_;
}
/// @dev Transfers the balance of {token} from the contract to the
/// sickle owner
/// @param token Address of the token to transfer
function transferTokenToUser(
address token
) public payable {
address recipient = Sickle(payable(address(this))).owner();
if (token == ETH || token == UNISWAP_ETH) {
uint256 wethBalance = weth.balanceOf(address(this));
if (wethBalance > 0) {
weth.withdraw(wethBalance);
}
if (address(this).balance > 0) {
SafeTransferLib.safeTransferETH(
recipient, address(this).balance
);
}
} else {
uint256 balance = IERC20(token).balanceOf(address(this));
if (balance > 0) {
SafeTransferLib.safeTransfer(token, recipient, balance);
}
}
}
/// @dev Transfers all balances of {tokens} and/or ETH from the contract
/// to the sickle owner
/// @param tokens An array of token addresses
function transferTokensToUser(
address[] memory tokens
) external payable checkTransfersTo(tokens) {
for (uint256 i; i != tokens.length;) {
transferTokenToUser(tokens[i]);
unchecked {
i++;
}
}
}
/// @dev Transfers {amountIn} of {tokenIn} from the user to the Sickle
/// contract, charging the fees and converting the amount to WETH if
/// necessary
/// @param tokenIn Address of the token to transfer
/// @param amountIn Amount of the token to transfer
/// @param strategy Address of the caller strategy
/// @param feeSelector Selector of the caller function
function transferTokenFromUser(
address tokenIn,
uint256 amountIn,
address strategy,
bytes4 feeSelector
) public payable checkTransferFrom(tokenIn, amountIn) {
_checkMsgValue(amountIn, tokenIn == ETH || tokenIn == UNISWAP_ETH);
_transferTokenFromUser(tokenIn, amountIn, strategy, feeSelector);
}
/// @dev Transfers {amountIn} of {tokenIn} from the user to the Sickle
/// contract, charging the fees and converting the amount to WETH if
/// necessary
/// @param tokensIn Addresses of the tokens to transfer
/// @param amountsIn Amounts of the tokens to transfer
/// @param strategy Address of the caller strategy
/// @param feeSelector Selector of the caller function
function transferTokensFromUser(
address[] memory tokensIn,
uint256[] memory amountsIn,
address strategy,
bytes4 feeSelector
) external payable checkTransfersFrom(tokensIn, amountsIn) {
bool hasEth = false;
for (uint256 i; i < tokensIn.length; i++) {
if (tokensIn[i] == ETH || tokensIn[i] == UNISWAP_ETH) {
_checkMsgValue(amountsIn[i], true);
hasEth = true;
}
_transferTokenFromUser(
tokensIn[i], amountsIn[i], strategy, feeSelector
);
}
if (!hasEth) {
// Revert if ETH was sent but not used
_checkMsgValue(0, false);
}
}
/* Internal functions */
function _transferTokenFromUser(
address tokenIn,
uint256 amountIn,
address strategy,
bytes4 feeSelector
) internal {
if (tokenIn != ETH && tokenIn != UNISWAP_ETH) {
SafeTransferLib.safeTransferFrom(
tokenIn,
Sickle(payable(address(this))).owner(),
address(this),
amountIn
);
}
bytes memory result = _delegateTo(
address(feesLib),
abi.encodeCall(
IFeesLib.chargeFee, (strategy, feeSelector, tokenIn, amountIn)
)
);
uint256 remainder = abi.decode(result, (uint256));
if (tokenIn == ETH) {
weth.deposit{ value: remainder }();
}
}
modifier checkTransferFrom(address tokenIn, uint256 amountIn) {
if (amountIn == 0) {
revert AmountInRequired();
}
_;
}
modifier checkTransfersFrom(
address[] memory tokensIn,
uint256[] memory amountsIn
) {
uint256 tokenLength = tokensIn.length;
if (tokenLength != amountsIn.length) {
revert ArrayLengthMismatch();
}
if (tokenLength == 0) {
revert TokenInRequired();
}
for (uint256 i; i < tokenLength; i++) {
if (amountsIn[i] == 0) {
revert AmountInRequired();
}
}
bool hasETH = false;
bool hasUniswapETH = false;
for (uint256 i; i < tokenLength; i++) {
if (tokensIn[i] == ETH) {
hasETH = true;
}
if (tokensIn[i] == UNISWAP_ETH) {
hasUniswapETH = true;
}
if (hasETH && hasUniswapETH) {
revert IncompatibleEthTokens();
}
for (uint256 j = i + 1; j < tokenLength; j++) {
if (tokensIn[i] == tokensIn[j]) {
revert DuplicateTokenIn();
}
}
}
_;
}
modifier checkTransfersTo(
address[] memory tokensOut
) {
uint256 tokenLength = tokensOut.length;
if (tokenLength == 0) {
revert TokenOutRequired();
}
_;
}
}
"
},
"contracts/libraries/ZapLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeTransferLib } from "solmate/utils/SafeTransferLib.sol";
import { AddLiquidityParams } from "contracts/structs/LiquidityStructs.sol";
import { ILiquidityConnector } from
"contracts/interfaces/ILiquidityConnector.sol";
import { ConnectorRegistry } from "contracts/ConnectorRegistry.sol";
import { DelegateModule } from "contracts/modules/DelegateModule.sol";
import { ZapIn, ZapOut } from "contracts/structs/ZapStructs.sol";
import { IZapLib } from "contracts/interfaces/libraries/IZapLib.sol";
import { ISwapLib } from "contracts/interfaces/libraries/ISwapLib.sol";
contract ZapLib is DelegateModule, IZapLib {
error LiquidityAmountError(); // 0x4d0ab6b4
ISwapLib public immutable swapLib;
ConnectorRegistry public immutable connectorRegistry;
constructor(ConnectorRegistry connectorRegistry_, ISwapLib swapLib_) {
connectorRegistry = connectorRegistry_;
swapLib = swapLib_;
}
function zapIn(
ZapIn memory zap
) external payable {
uint256 swapDataLength = zap.swaps.length;
for (uint256 i; i < swapDataLength;) {
_delegateTo(
address(swapLib), abi.encodeCall(ISwapLib.swap, (zap.swaps[i]))
);
unchecked {
i++;
}
}
if (zap.addLiquidityParams.lpToken == address(0)) {
return;
}
bool atLeastOneNonZero = false;
AddLiquidi
Submitted on: 2025-09-17 13:38:57
Comments
Log in to comment.
No comments yet.