diff --git a/contracts/aggregator/fantom/flashloan/helper.sol b/contracts/aggregator/fantom/flashloan/helper.sol new file mode 100644 index 00000000..6e22eb1d --- /dev/null +++ b/contracts/aggregator/fantom/flashloan/helper.sol @@ -0,0 +1,74 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import "../../../common/helpers.sol"; +import "./variable.sol"; + +contract Helper is Variables, TokenHelpers, FlashloanHelpers { + using SafeERC20 for IERC20; + + /** + * @dev Returns to true if the passed address is a DSA else returns false. + * @notice Returns to true if the passed address is a DSA else returns false. + * @param _account account to check for, if DSA. + */ + function checkIfDsa(address _account) internal view returns (bool) { + return instaList.accountID(_account) > 0; + } + + /** + * @dev Returns fee for the passed route in BPS. + * @notice Returns fee for the passed route in BPS. 1 BPS == 0.01%. + * @param _route route number for flashloan. + */ + function calculateFeeBPS(uint256 _route) + public + view + virtual + returns (uint256 BPS_) + { + bytes memory _output = Address.functionStaticCall( + routeToImplementation[_route], + msg.data, + "calculateFeeBPS-call-failed" + ); + BPS_ = abi.decode(_output, (uint256)); + } + + /** + * @dev Function to get the list of all routes. + * @notice Function to get the list of all routes. + */ + function getRoutes() public view returns (uint256[] memory) { + return routes; + } + + /** + * @dev Function to get the list of enabled routes. + * @notice Function to get the list of enabled routes. + */ + function getEnabledRoutes() + public + view + returns (uint16[] memory routesEnabled_) + { + uint256[] memory routesAll_ = getRoutes(); + uint256 length = routesAll_.length; + uint256 _count = 0; + + for (uint256 i = 0; i < length; i++) { + if (routeStatus[routesAll_[i]] == true) { + _count++; + } + } + + routesEnabled_ = new uint16[](_count); + uint256 k = 0; + + for (uint256 j = 0; j < length; j++) { + if (routeStatus[routesAll_[j]]) { + routesEnabled_[k] = uint16(routesAll_[j]); + k++; + } + } + } +} diff --git a/contracts/aggregator/fantom/flashloan/main.sol b/contracts/aggregator/fantom/flashloan/main.sol new file mode 100644 index 00000000..c71197ab --- /dev/null +++ b/contracts/aggregator/fantom/flashloan/main.sol @@ -0,0 +1,200 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import "./helper.sol"; + +/** + * @title Flashloan. + * @dev Flashloan aggregator for Fantom. + */ + +contract AdminModule is Helper { + using SafeERC20 for IERC20; + + event updateOwnerLog(address indexed oldOwner, address indexed newOwner); + + event updateWhitelistLog( + address indexed account, + bool indexed isWhitelisted_ + ); + + event LogCollectRevenue(address to, address[] tokens, uint256[] amounts); + + /** + * @dev Update owner. + * @notice Update owner. + * @param newOwner_ address of new owner. + */ + function updateOwner(address newOwner_) external onlyOwner { + address oldOwner_ = owner; + owner = newOwner_; + emit updateOwnerLog(oldOwner_, newOwner_); + } + + /** + * @dev Function to add new routes. + * @notice Function to add new routes and implementations. + * @param _routes routes to add. + * @param _impls implementations of their respective routes. + */ + function addNewRoutesAndEnable( + uint256[] memory _routes, + address[] memory _impls + ) public onlyOwner { + require(_routes.length == _impls.length, "lengths-dont-match"); + uint256 length = _routes.length; + for (uint256 i = 0; i < length; i++) { + require( + routeToImplementation[_routes[i]] == address(0), + "route-already-exists" + ); + routeToImplementation[_routes[i]] = _impls[i]; + routeStatus[_routes[i]] = true; + routes.push(_routes[i]); + } + } + + /** + * @dev Function to update existing routes. + * @notice Function to update existing routes and implementations. + * @param _routes routes to update. + * @param _impls implementations of their respective routes. + */ + function updateRouteImplementations( + uint256[] memory _routes, + address[] memory _impls + ) public onlyOwner { + require(_routes.length == _impls.length, "lengths-dont-match"); + uint256 length = _routes.length; + for (uint256 i = 0; i < length; i++) { + require( + routeToImplementation[_routes[i]] != address(0), + "route-doesnt-exist" + ); + routeToImplementation[_routes[i]] = _impls[i]; + } + } + + /** + * @dev Function to change route status. + * @notice Function to enable and disable routes. + * @param _routes routes those status we want to change. + * @param _statuses new statuses. + */ + function changeRouteStatus( + uint256[] memory _routes, + bool[] memory _statuses + ) public onlyOwner { + require(_routes.length == _statuses.length, "lengths-dont-match"); + uint256 length = _routes.length; + for (uint256 i = 0; i < length; i++) { + routeStatus[_routes[i]] = _statuses[i]; + } + } + + /** + * @dev Function to delete route. + * @notice Function to delete route. + * @param _route routes to delete. + */ + function deleteRoute(uint256 _route) public onlyOwner { + uint256 length = routes.length; + for (uint256 i = 0; i < length; i++) { + if (routes[i] == _route) { + routes[i] = routes[length - 1]; + routes.pop(); + delete routeToImplementation[_route]; + delete routeStatus[_route]; + } + } + } + + /** + * @dev Function to transfer fee to the treasury. + * @notice Function to transfer fee to the treasury. Will be called manually. + * @param _tokens token addresses for transferring fee to treasury. + * @param _to treasury address. + */ + function transferFee(address[] memory _tokens, address _to) + public + onlyOwner + { + uint256[] memory _amts = new uint256[](_tokens.length); + for (uint256 i = 0; i < _tokens.length; i++) { + IERC20 token_ = IERC20(_tokens[i]); + uint256 decimals_ = TokenInterface(_tokens[i]).decimals(); + uint256 amtToSub_ = decimals_ == 18 ? 1e10 : decimals_ > 12 + ? 10000 + : decimals_ > 7 + ? 100 + : 10; + _amts[i] = token_.balanceOf(address(this)) > amtToSub_ + ? (token_.balanceOf(address(this)) - amtToSub_) + : 0; + if (_amts[i] > 0) token_.safeTransfer(_to, _amts[i]); + } + emit LogCollectRevenue(_to, _tokens, _amts); + } +} + +contract FlashAggregatorFantom is AdminModule { + event LogFlashloan(address indexed account, uint256 indexed route, address[] tokens, uint256[] amounts); + + /** + * @dev Main function for flashloan for all routes. Calls the middle functions according to routes. + * @notice Main function for flashloan for all routes. Calls the middle functions according to routes. + * @param _tokens token addresses for flashloan. + * @param _amounts list of amounts for the corresponding assets. + * @param _route route for flashloan. + * @param _data extra data passed. + */ + function flashLoan( + address[] memory _tokens, + uint256[] memory _amounts, + uint256 _route, + bytes calldata _data, + bytes calldata // kept for future use by instadapp. Currently not used anywhere. + ) external { + require(_tokens.length == _amounts.length, "array-lengths-not-same"); + require(routeStatus[_route] == true, "route-disabled"); + + fallbackImplementation = routeToImplementation[_route]; + + Address.functionDelegateCall( + fallbackImplementation, + msg.data, + "call-to-impl-failed" + ); + + delete fallbackImplementation; + emit LogFlashloan(msg.sender, _route, _tokens, _amounts); + } +} + +contract InstaFlashAggregatorFantom is FlashAggregatorFantom { + function initialize( + address owner_, + address aave_, + address fla_ + ) public { + require(status == 0, "cannot-call-again"); + owner = owner_; + status = 1; + routeToImplementation[9] = aave_; + routeStatus[9] = true; + routes.push(9); + routeToImplementation[10] = fla_; + routeStatus[10] = true; + routes.push(10); + } + + // Fallback function + fallback(bytes calldata input) external payable returns (bytes memory output) { + output = Address.functionDelegateCall( + fallbackImplementation, + input, + "fallback-impl-call-failed" + ); + } + + receive() external payable {} +} diff --git a/contracts/aggregator/fantom/flashloan/routes/aaveV3/main.sol b/contracts/aggregator/fantom/flashloan/routes/aaveV3/main.sol new file mode 100644 index 00000000..2705582e --- /dev/null +++ b/contracts/aggregator/fantom/flashloan/routes/aaveV3/main.sol @@ -0,0 +1,162 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import "../../helper.sol"; + +interface IAaveV3Lending { + function flashLoan( + address receiverAddress, + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata modes, + address onBehalfOf, + bytes calldata params, + uint16 referralCode + ) external; + + function FLASHLOAN_PREMIUM_TOTAL() external view returns (uint128); +} + +contract AaveV3ConstantVariables { + address public constant aaveV3LendingAddr = + 0x794a61358D6845594F94dc1DB02A252b5b4814aD; + IAaveV3Lending public constant aaveV3Lending = + IAaveV3Lending(aaveV3LendingAddr); +} + +contract AaveV3RouteImplementationFantom is AaveV3ConstantVariables, Helper { + /** + * @dev Main function for flashloan for all routes. Calls the middle functions according to routes. + * @notice Main function for flashloan for all routes. Calls the middle functions according to routes. + * @param _tokens token addresses for flashloan. + * @param _amounts list of amounts for the corresponding assets. + * @param _route route for flashloan. + * @param _data extra data passed. + */ + function flashLoan( + address[] memory _tokens, + uint256[] memory _amounts, + uint256 _route, + bytes calldata _data, + bytes calldata // kept for future use by instadapp. Currently not used anywhere. + ) external reentrancy { + require(_route == 9, "invalid-AAVE-route"); + (_tokens, _amounts) = bubbleSort(_tokens, _amounts); + validateTokens(_tokens); + routeAaveV3(_tokens, _amounts, _data); + } + + /** + * @dev Returns fee for the passed route in BPS. + * @notice Returns fee for the passed route in BPS. 1 BPS == 0.01%. + * @param _route route number for flashloan. + */ + function calculateFeeBPS(uint256 _route) + public + view + override + returns (uint256 BPS_) + { + if (_route == 9) { + BPS_ = aaveV3Lending.FLASHLOAN_PREMIUM_TOTAL(); + } else { + revert("Invalid source"); + } + if (BPS_ < InstaFeeBPS) { + BPS_ = InstaFeeBPS; + } + } + + /** + * @dev Callback function for aave flashloan. + * @notice Callback function for aave flashloan. + * @param _assets list of asset addresses for flashloan. + * @param _amounts list of amounts for the corresponding assets for flashloan. + * @param _premiums list of premiums/fees for the corresponding addresses for flashloan. + * @param _initiator initiator address for flashloan. + * @param _data extra data passed. + */ + function executeOperation( + address[] memory _assets, + uint256[] memory _amounts, + uint256[] memory _premiums, + address _initiator, + bytes memory _data + ) external verifyDataHash(_data) returns (bool) { + require(_initiator == address(this), "not-same-sender"); + require(msg.sender == aaveV3LendingAddr, "not-aave-sender"); + FlashloanVariables memory instaLoanVariables_; + + (address sender_, bytes memory data_) = abi.decode( + _data, + (address, bytes) + ); + + instaLoanVariables_._tokens = _assets; + instaLoanVariables_._amounts = _amounts; + instaLoanVariables_._instaFees = calculateFees( + _amounts, + calculateFeeBPS(9) + ); + instaLoanVariables_._iniBals = calculateBalances( + _assets, + address(this) + ); + + safeApprove(instaLoanVariables_, _premiums, aaveV3LendingAddr); + safeTransfer(instaLoanVariables_, sender_); + + if (checkIfDsa(sender_)) { + Address.functionCall( + sender_, + data_, + "DSA-flashloan-fallback-failed" + ); + } else { + InstaFlashReceiverInterface(sender_).executeOperation( + _assets, + _amounts, + instaLoanVariables_._instaFees, + sender_, + data_ + ); + } + + instaLoanVariables_._finBals = calculateBalances( + _assets, + address(this) + ); + validateFlashloan(instaLoanVariables_); + + return true; + } + + /** + * @dev Middle function for route 9. + * @notice Middle function for route 9. + * @param _tokens list of token addresses for flashloan. + * @param _amounts list of amounts for the corresponding assets or amount of ether to borrow as collateral for flashloan. + * @param _data extra data passed. + */ + function routeAaveV3( + address[] memory _tokens, + uint256[] memory _amounts, + bytes memory _data + ) internal { + bytes memory data_ = abi.encode(msg.sender, _data); + uint256 length_ = _tokens.length; + uint256[] memory _modes = new uint256[](length_); + for (uint256 i = 0; i < length_; i++) { + _modes[i] = 0; + } + dataHash = bytes32(keccak256(data_)); + aaveV3Lending.flashLoan( + address(this), + _tokens, + _amounts, + _modes, + address(0), + data_, + 3228 + ); + } +} diff --git a/contracts/aggregator/fantom/flashloan/routes/fla/main.sol b/contracts/aggregator/fantom/flashloan/routes/fla/main.sol new file mode 100644 index 00000000..e1f0109a --- /dev/null +++ b/contracts/aggregator/fantom/flashloan/routes/fla/main.sol @@ -0,0 +1,94 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import "../../helper.sol"; + +contract FLAImplementationFantom is Helper { + /** + * @dev Main function for flashloan for all routes. Calls the middle functions according to routes. + * @notice Main function for flashloan for all routes. Calls the middle functions according to routes. + * @param _tokens token addresses for flashloan. + * @param _amounts list of amounts for the corresponding assets. + * @param _route route for flashloan. + * @param _data extra data passed. + */ + function flashLoan( + address[] memory _tokens, + uint256[] memory _amounts, + uint256 _route, + bytes calldata _data, + bytes calldata // kept for future use by instadapp. Currently not used anywhere. + ) external reentrancy { + require(_route == 10, "invalid-FLA-route"); + (_tokens, _amounts) = bubbleSort(_tokens, _amounts); + validateTokens(_tokens); + routeFLA(msg.sender, _tokens, _amounts, _data); + } + + /** + * @dev Returns fee for the passed route in BPS. + * @notice Returns fee for the passed route in BPS. 1 BPS == 0.01%. + * @param _route route number for flashloan. + */ + function calculateFeeBPS(uint256 _route) + public + view + override + returns (uint256 BPS_) + { + if (_route == 10) { + BPS_ = InstaFeeBPS; + } else { + revert("Invalid source"); + } + } + + function routeFLA( + address _receiverAddress, + address[] memory _tokens, + uint256[] memory _amounts, + bytes memory _data + ) internal returns (bool) { + FlashloanVariables memory instaLoanVariables_; + instaLoanVariables_._tokens = _tokens; + instaLoanVariables_._amounts = _amounts; + instaLoanVariables_._instaFees = calculateFees( + _amounts, + calculateFeeBPS(10) + ); + instaLoanVariables_._iniBals = calculateBalances( + _tokens, + address(this) + ); + + safeTransfer(instaLoanVariables_, _receiverAddress); + + if (checkIfDsa(_receiverAddress)) { + Address.functionCall( + _receiverAddress, + _data, + "DSA-flashloan-fallback-failed" + ); + } else { + require( + InstaFlashReceiverInterface(_receiverAddress).executeOperation( + _tokens, + _amounts, + instaLoanVariables_._instaFees, + _receiverAddress, + _data + ), + "invalid flashloan execution" + ); + } + + instaLoanVariables_._finBals = calculateBalances( + _tokens, + address(this) + ); + + validateFlashloan(instaLoanVariables_); + + status = 1; + return true; + } +} diff --git a/contracts/aggregator/fantom/flashloan/variable.sol b/contracts/aggregator/fantom/flashloan/variable.sol new file mode 100644 index 00000000..e1884e00 --- /dev/null +++ b/contracts/aggregator/fantom/flashloan/variable.sol @@ -0,0 +1,51 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +import "../../../common/interfaces.sol"; +import "../../../common/variablesV1.sol"; +import "../../../common/variablesV2.sol"; +import "../../../common/structs.sol"; + +contract ConstantVariables { + address public constant treasuryAddr = + 0x6C4061A00F8739d528b185CC683B6400E0cd396a; + ListInterface public constant instaList = ListInterface(0x10e166c3FAF887D8a61dE6c25039231eE694E926); + uint256 public constant InstaFeeBPS = 5; // in BPS; 1 BPS = 0.01% +} + +contract FantomVariablesV1 { + address public owner; + + /** + * @dev owner gaurd. + * @notice owner gaurd. + */ + modifier onlyOwner() { + require(msg.sender == owner, "not-owner"); + _; + } +} + +contract Variables is ConstantVariables, CommonVariablesV1, Structs, FantomVariablesV1, CommonVariablesV2 { + // ******** Stroage Variable layout ******* // + + /* CommonVariablesV1 + // bytes32 internal dataHash; + // uint256 internal status; + */ + + /* Structs + FlashloanVariables; + */ + + /* FantomVariablesV1 + // address public owner; + */ + + /* CommonVariablesV2 + // mapping(uint256 => address) public routeToImplementation; + // mapping(uint256 => bool) public routeStatus; + // address internal fallbackImplementation; + // uint256[] public routes; + */ +} \ No newline at end of file diff --git a/contracts/common/helpers.sol b/contracts/common/helpers.sol new file mode 100644 index 00000000..c420503a --- /dev/null +++ b/contracts/common/helpers.sol @@ -0,0 +1,164 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; +import "./interfaces.sol"; +import "./variablesV2.sol"; +import "./structs.sol"; + +contract TokenHelpers is Structs { + using SafeERC20 for IERC20; + + /** + * @dev Approves the token to the spender address with allowance amount. + * @notice Approves the token to the spender address with allowance amount. + * @param token_ token for which allowance is to be given. + * @param spender_ the address to which the allowance is to be given. + * @param amount_ amount of token. + */ + function approve( + address token_, + address spender_, + uint256 amount_ + ) internal { + TokenInterface tokenContract_ = TokenInterface(token_); + try tokenContract_.approve(spender_, amount_) {} catch { + IERC20 token = IERC20(token_); + token.safeApprove(spender_, 0); + token.safeApprove(spender_, amount_); + } + } + + /** + * @dev Calculates the balances.. + * @notice Calculates the balances of the account passed for the tokens. + * @param _tokens list of token addresses to calculate balance for. + * @param _account account to calculate balance for. + */ + function calculateBalances(address[] memory _tokens, address _account) internal view returns (uint256[] memory) { + uint256 _length = _tokens.length; + uint256[] memory balances_ = new uint256[](_length); + for (uint256 i = 0; i < _length; i++) { + IERC20 token = IERC20(_tokens[i]); + balances_[i] = token.balanceOf(_account); + } + return balances_; + } + + /** + * @dev Validates if token addresses are unique. Just need to check adjacent tokens as the array was sorted first + * @notice Validates if token addresses are unique. + * @param _tokens list of token addresses. + */ + function validateTokens(address[] memory _tokens) internal pure { + for (uint256 i = 0; i < _tokens.length - 1; i++) { + require(_tokens[i] != _tokens[i + 1], 'non-unique-tokens'); + } + } + + /** + * @dev Sort the tokens and amounts arrays according to token addresses. + * @notice Sort the tokens and amounts arrays according to token addresses. + * @param _token0 address of token0. + * @param _token1 address of token1. + */ + function sortTokens(address _token0, address _token1) internal pure returns (address, address) { + if (_token1 < _token0) { + (_token0, _token1) = (_token1, _token0); + } + return (_token0, _token1); + } + + /** + * @dev Approves the tokens to the receiver address with allowance (amount + fee). + * @notice Approves the tokens to the receiver address with allowance (amount + fee). + * @param _instaLoanVariables struct which includes list of token addresses and amounts. + * @param _fees list of premiums/fees for the corresponding addresses for flashloan. + * @param _receiver address to which tokens have to be approved. + */ + function safeApprove( + FlashloanVariables memory _instaLoanVariables, + uint256[] memory _fees, + address _receiver + ) internal { + uint256 length_ = _instaLoanVariables._tokens.length; + require(length_ == _instaLoanVariables._amounts.length, 'Lengths of parameters not same'); + require(length_ == _fees.length, 'Lengths of parameters not same'); + for (uint256 i = 0; i < length_; i++) { + approve(_instaLoanVariables._tokens[i], _receiver, _instaLoanVariables._amounts[i] + _fees[i]); + } + } + + /** + * @dev Transfers the tokens to the receiver address. + * @notice Transfers the tokens to the receiver address. + * @param _instaLoanVariables struct which includes list of token addresses and amounts. + * @param _receiver address to which tokens have to be transferred. + */ + function safeTransfer(FlashloanVariables memory _instaLoanVariables, address _receiver) internal { + uint256 length_ = _instaLoanVariables._tokens.length; + require(length_ == _instaLoanVariables._amounts.length, 'Lengths of parameters not same'); + for (uint256 i = 0; i < length_; i++) { + IERC20 token = IERC20(_instaLoanVariables._tokens[i]); + token.safeTransfer(_receiver, _instaLoanVariables._amounts[i]); + } + } +} + +contract FlashloanHelpers is Structs { + /** + * @dev Calculate fees for the respective amounts and fee in BPS passed. + * @notice Calculate fees for the respective amounts and fee in BPS passed. 1 BPS == 0.01%. + * @param _amounts list of amounts. + * @param _BPS fee in BPS. + */ + function calculateFees(uint256[] memory _amounts, uint256 _BPS) internal pure returns (uint256[] memory) { + uint256 length_ = _amounts.length; + uint256[] memory InstaFees = new uint256[](length_); + for (uint256 i = 0; i < length_; i++) { + InstaFees[i] = (_amounts[i] * _BPS) / (10**4); + } + return InstaFees; + } + + /** + * @dev Sort the tokens and amounts arrays according to token addresses. + * @notice Sort the tokens and amounts arrays according to token addresses. + * @param _tokens list of token addresses. + * @param _amounts list of respective amounts. + */ + function bubbleSort(address[] memory _tokens, uint256[] memory _amounts) + internal + pure + returns (address[] memory, uint256[] memory) + { + for (uint256 i = 0; i < _tokens.length - 1; i++) { + for (uint256 j = 0; j < _tokens.length - i - 1; j++) { + if (_tokens[j] > _tokens[j + 1]) { + (_tokens[j], _tokens[j + 1], _amounts[j], _amounts[j + 1]) = ( + _tokens[j + 1], + _tokens[j], + _amounts[j + 1], + _amounts[j] + ); + } + } + } + return (_tokens, _amounts); + } + + + /** + * @dev Validates if the receiver sent the correct amounts of funds. + * @notice Validates if the receiver sent the correct amounts of funds. + * @param _instaLoanVariables struct which includes list of initial balances, final balances and fees for the respective tokens. + */ + function validateFlashloan(FlashloanVariables memory _instaLoanVariables) internal pure { + for (uint256 i = 0; i < _instaLoanVariables._iniBals.length; i++) { + require( + _instaLoanVariables._iniBals[i] + _instaLoanVariables._instaFees[i] <= _instaLoanVariables._finBals[i], + 'amount-paid-less' + ); + } + } +} \ No newline at end of file diff --git a/contracts/common/interfaces.sol b/contracts/common/interfaces.sol new file mode 100644 index 00000000..5a88a056 --- /dev/null +++ b/contracts/common/interfaces.sol @@ -0,0 +1,45 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +pragma experimental ABIEncoderV2; + +interface InstaFlashReceiverInterface { + function executeOperation( + address[] calldata assets, + uint256[] calldata amounts, + uint256[] calldata premiums, + address initiator, + bytes calldata _data + ) external returns (bool); +} + +interface IndexInterface { + function master() external view returns (address); + + function list() external view returns (address); +} + +interface ListInterface { + function accountID(address) external view returns (uint64); +} + +interface TokenInterface { + function approve(address, uint256) external; + + function transfer(address, uint256) external; + + function transferFrom( + address, + address, + uint256 + ) external; + + function deposit() external payable; + + function withdraw(uint256) external; + + function balanceOf(address) external view returns (uint256); + + function decimals() external view returns (uint256); + + function totalSupply() external view returns (uint256); +} diff --git a/contracts/common/structs.sol b/contracts/common/structs.sol new file mode 100644 index 00000000..faabcd6b --- /dev/null +++ b/contracts/common/structs.sol @@ -0,0 +1,12 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +contract Structs { + struct FlashloanVariables { + address[] _tokens; + uint256[] _amounts; + uint256[] _iniBals; + uint256[] _finBals; + uint256[] _instaFees; + } +} diff --git a/contracts/common/variablesV1.sol b/contracts/common/variablesV1.sol new file mode 100644 index 00000000..9b1480ad --- /dev/null +++ b/contracts/common/variablesV1.sol @@ -0,0 +1,34 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +contract CommonVariablesV1 { + + bytes32 internal dataHash; + // if 1 then can enter flashlaon, if 2 then callback + uint256 internal status; + + /** + * @dev better checking by double encoding the data. + * @notice better checking by double encoding the data. + * @param data_ data passed. + */ + modifier verifyDataHash(bytes memory data_) { + bytes32 dataHash_ = keccak256(data_); + require(dataHash_ == dataHash && dataHash_ != bytes32(0), 'invalid-data-hash'); + require(status == 2, 'already-entered'); + dataHash = bytes32(0); + _; + status = 1; + } + + /** + * @dev reentrancy gaurd. + * @notice reentrancy gaurd. + */ + modifier reentrancy() { + require(status == 1, 'already-entered'); + status = 2; + _; + require(status == 1, 'already-entered'); + } +} diff --git a/contracts/common/variablesV2.sol b/contracts/common/variablesV2.sol new file mode 100644 index 00000000..3853a318 --- /dev/null +++ b/contracts/common/variablesV2.sol @@ -0,0 +1,9 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +contract CommonVariablesV2 { + mapping(uint256 => address) public routeToImplementation; + mapping(uint256 => bool) public routeStatus; + address internal fallbackImplementation; + uint256[] public routes; +} \ No newline at end of file diff --git a/contracts/misc/InstaReceiver.sol b/contracts/misc/InstaReceiver.sol index 2ae239fd..61333919 100644 --- a/contracts/misc/InstaReceiver.sol +++ b/contracts/misc/InstaReceiver.sol @@ -45,6 +45,7 @@ contract InstaFlashReceiver { amounts[i] + premiums[i] ); } + return true; } constructor(address flashloan_) { diff --git a/contracts/resolver/fantom/helpers.sol b/contracts/resolver/fantom/helpers.sol new file mode 100644 index 00000000..1efb9371 --- /dev/null +++ b/contracts/resolver/fantom/helpers.sol @@ -0,0 +1,75 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import {Variables} from "./variables.sol"; +import {InstaFlashloanAggregatorInterface} from "./interfaces.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; + +contract Helper is Variables { + function getRoutesAndAvailability( + address[] memory _tokens, + uint256[] memory _amounts + ) internal view returns (uint16[] memory routes_, uint16[] memory routesWithAvailability_) { + uint16[] memory _enabledRoutes = flashloanAggregator.getEnabledRoutes(); + uint256 length = _enabledRoutes.length; + uint256 j = 0; + uint16[] memory _routesWithAvailability = new uint16[](length); + for (uint256 i = 0; i < length; i++) { + if (getAvailability(_enabledRoutes[i], _tokens, _amounts)) { + _routesWithAvailability[j] = _enabledRoutes[i]; + j++; + } else { + require(false, "invalid-route-2"); + } + } + return (_enabledRoutes, _routesWithAvailability); + } + + function getAvailability( + uint256 _route, + address[] memory _tokens, + uint256[] memory _amounts + ) public view returns (bool) { + bytes memory _output = Address.functionStaticCall( + routeToResolver[_route], + abi.encodeWithSelector( + this.getAvailability.selector, + _route, + _tokens, + _amounts + ), + "getAvailability-call-failed" + ); + return abi.decode(_output, (bool)); + } + + function bubbleSort(address[] memory _tokens, uint256[] memory _amounts) + internal + pure + returns (address[] memory, uint256[] memory) + { + for (uint256 i = 0; i < _tokens.length - 1; i++) { + for (uint256 j = 0; j < _tokens.length - i - 1; j++) { + if (_tokens[j] > _tokens[j + 1]) { + ( + _tokens[j], + _tokens[j + 1], + _amounts[j], + _amounts[j + 1] + ) = ( + _tokens[j + 1], + _tokens[j], + _amounts[j + 1], + _amounts[j] + ); + } + } + } + return (_tokens, _amounts); + } + + function validateTokens(address[] memory _tokens) internal pure { + for (uint256 i = 0; i < _tokens.length - 1; i++) { + require(_tokens[i] != _tokens[i + 1], "non-unique-tokens"); + } + } +} diff --git a/contracts/resolver/fantom/interfaces.sol b/contracts/resolver/fantom/interfaces.sol new file mode 100644 index 00000000..bda3904f --- /dev/null +++ b/contracts/resolver/fantom/interfaces.sol @@ -0,0 +1,8 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +interface InstaFlashloanAggregatorInterface { + function getRoutes() external pure returns (uint16[] memory); + function getEnabledRoutes() external view returns (uint16[] memory _routes); + function calculateFeeBPS(uint256 _route) external view returns (uint256); +} diff --git a/contracts/resolver/fantom/main.sol b/contracts/resolver/fantom/main.sol new file mode 100644 index 00000000..1fddfb46 --- /dev/null +++ b/contracts/resolver/fantom/main.sol @@ -0,0 +1,161 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; + +import {Helper} from "./helpers.sol"; +import {InstaFlashloanAggregatorInterface} from "./interfaces.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract AdminModule is Helper { + event updateOwnerLog(address indexed oldOwner, address indexed newOwner); + + /** + * @dev owner gaurd. + * @notice owner gaurd. + */ + modifier onlyOwner() { + require(msg.sender == owner, "not-owner"); + _; + } + + /** + * @dev Update owner. + * @notice Update owner. + * @param newOwner_ address of new owner. + */ + function updateOwner(address newOwner_) external onlyOwner { + address oldOwner_ = owner; + owner = newOwner_; + emit updateOwnerLog(oldOwner_, newOwner_); + } + + /** + * @dev Function to add new routes. + * @notice Function to add new routes and implementations. + * @param _routes routes to add. + * @param _resolverImpls implementations of their respective routes. + */ + function addNewRoutes( + uint256[] memory _routes, + address[] memory _resolverImpls + ) public onlyOwner { + require(_routes.length == _resolverImpls.length, "lengths-dont-match"); + uint256 length = _routes.length; + for (uint256 i = 0; i < length; i++) { + require( + routeToResolver[_routes[i]] == address(0), + "route-already-added" + ); + routeToResolver[_routes[i]] = _resolverImpls[i]; + } + } + + /** + * @dev Function to update existing routes. + * @notice Function to update existing routes and implementations. + * @param _routes routes to update. + * @param _resolverImpls implementations of their respective routes. + */ + function updateResolverImplementations( + uint256[] memory _routes, + address[] memory _resolverImpls + ) public onlyOwner { + require(_routes.length == _resolverImpls.length, "lengths-dont-match"); + uint256 length = _routes.length; + for (uint256 i = 0; i < length; i++) { + routeToResolver[_routes[i]] = _resolverImpls[i]; + } + } +} + +contract FlashResolverFantom is AdminModule { + function getRoutesInfo() + public + view + returns (uint16[] memory routes_, uint256[] memory fees_) + { + routes_ = flashloanAggregator.getEnabledRoutes(); + fees_ = new uint256[](routes_.length); + for (uint256 i = 0; i < routes_.length; i++) { + fees_[i] = flashloanAggregator.calculateFeeBPS(uint256(routes_[i])); + } + } + + function getBestRoutes(address[] memory _tokens, uint256[] memory _amounts) + public + view + returns (uint16[] memory, uint256) + { + require(_tokens.length == _amounts.length, "array-lengths-not-same"); + + (_tokens, _amounts) = bubbleSort(_tokens, _amounts); + validateTokens(_tokens); + + uint16[] memory bRoutes_; + uint256 feeBPS_; + ( + uint16[] memory routes_, + uint16[] memory routesWithAvailability_ + ) = getRoutesAndAvailability(_tokens, _amounts); + + uint16 j = 0; + bRoutes_ = new uint16[](routes_.length); + feeBPS_ = type(uint256).max; + for (uint256 i = 0; i < routesWithAvailability_.length; i++) { + if (routesWithAvailability_[i] != 0) { + uint256 routeFeeBPS_ = flashloanAggregator.calculateFeeBPS( + uint256(routesWithAvailability_[i]) + ); + if (feeBPS_ > routeFeeBPS_) { + feeBPS_ = routeFeeBPS_; + bRoutes_[0] = routesWithAvailability_[i]; + j = 1; + } else if (feeBPS_ == routeFeeBPS_) { + bRoutes_[j] = routesWithAvailability_[i]; + j++; + } + } + } + uint16[] memory bestRoutes_ = new uint16[](j); + for (uint256 i = 0; i < j; i++) { + bestRoutes_[i] = bRoutes_[i]; + } + return (bestRoutes_, feeBPS_); + } + + function getData(address[] memory _tokens, uint256[] memory _amounts) + public + view + returns ( + uint16[] memory routes_, + uint256[] memory fees_, + uint16[] memory bestRoutes_, + uint256 bestFee_ + ) + { + (routes_, fees_) = getRoutesInfo(); + (bestRoutes_, bestFee_) = getBestRoutes(_tokens, _amounts); + return (routes_, fees_, bestRoutes_, bestFee_); + } +} + +contract InstaFlashloanResolverFantom is FlashResolverFantom { + function initialize( + address owner_, + uint256[] memory _routes, + address[] memory _resolverImpls + ) public { + require(status == 0, "cannot-call-again"); + require(_routes.length == _resolverImpls.length, "lengths-dont-match"); + owner = owner_; + uint256 length = _routes.length; + for (uint256 i = 0; i < length; i++) { + require( + routeToResolver[_routes[i]] == address(0), + "route-already-added" + ); + routeToResolver[_routes[i]] = _resolverImpls[i]; + } + status = 1; + } + receive() external payable {} +} diff --git a/contracts/resolver/fantom/routes/aaveV3.sol b/contracts/resolver/fantom/routes/aaveV3.sol new file mode 100644 index 00000000..d1dab9c9 --- /dev/null +++ b/contracts/resolver/fantom/routes/aaveV3.sol @@ -0,0 +1,56 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IAaveV3DataProvider { + function getReserveConfigurationData(address asset) + external + view + returns ( + uint256, + uint256, + uint256, + uint256, + uint256, + bool, + bool, + bool, + bool, + bool + ); + + function getReserveTokensAddresses(address asset) + external + view + returns ( + address, + address, + address + ); +} + +contract Variable { + IAaveV3DataProvider public constant aaveV3DataProvider = + IAaveV3DataProvider(0x69FA688f1Dc47d4B5d8029D5a35FB7a548310654); +} + +contract AaveV3Resolver is Variable { + function getAvailability( + uint256 _route, + address[] memory _tokens, + uint256[] memory _amounts + ) public view returns (bool) { + require(_route == 9, "invalid-route"); + uint256 length = _tokens.length; + for (uint256 i = 0; i < length; i++) { + IERC20 token_ = IERC20(_tokens[i]); + (, , , , , , , , bool isActive, ) = aaveV3DataProvider + .getReserveConfigurationData(_tokens[i]); + (address aTokenAddr, , ) = aaveV3DataProvider + .getReserveTokensAddresses(_tokens[i]); + if (isActive == false) return false; + if (token_.balanceOf(aTokenAddr) < _amounts[i]) return false; + } + return true; + } +} diff --git a/contracts/resolver/fantom/routes/fla.sol b/contracts/resolver/fantom/routes/fla.sol new file mode 100644 index 00000000..8767f2c4 --- /dev/null +++ b/contracts/resolver/fantom/routes/fla.sol @@ -0,0 +1,27 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract FLAResolver { + address public flashloanAggregatorAddr = + 0x22ed23Cc6EFf065AfDb7D5fF0CBf6886fd19aee1; + + function initialize(address fla) public { + flashloanAggregatorAddr = fla; + } + + function getAvailability( + uint256 _route, + address[] memory _tokens, + uint256[] memory _amounts + ) public view returns (bool) { + require(_route == 10, "invalid-route"); + uint256 length = _tokens.length; + for (uint256 i = 0; i < length; i++) { + IERC20 token_ = IERC20(_tokens[i]); + if (token_.balanceOf(flashloanAggregatorAddr) < _amounts[i]) + return false; + } + return true; + } +} diff --git a/contracts/resolver/fantom/variables.sol b/contracts/resolver/fantom/variables.sol new file mode 100644 index 00000000..17e97700 --- /dev/null +++ b/contracts/resolver/fantom/variables.sol @@ -0,0 +1,13 @@ +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.0; +import "./interfaces.sol"; + +contract Variables { + uint256 internal status; + address public owner; + address public flashloanAggregatorAddr = + 0x22ed23Cc6EFf065AfDb7D5fF0CBf6886fd19aee1; + InstaFlashloanAggregatorInterface public flashloanAggregator = + InstaFlashloanAggregatorInterface(flashloanAggregatorAddr); + mapping(uint256 => address) public routeToResolver; +} diff --git a/hardhat.config.ts b/hardhat.config.ts index 01fbd4f6..9a3d2df0 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -26,6 +26,7 @@ const chainIds = { avalanche: 43114, polygon: 137, optimism: 10, + fantom: 250, }; // Ensure that we have all the environment variables we need. @@ -59,6 +60,7 @@ function getNetworkUrl(networkType: string) { else if (networkType === "polygon") return `https://polygon-mainnet.g.alchemy.com/v2/${alchemyApiKey}`; else if (networkType === "arbitrum") return `https://arb-mainnet.g.alchemy.com/v2/${alchemyApiKey}`; else if (networkType === "optimism") return `https://opt-mainnet.g.alchemy.com/v2/${alchemyApiKey}`; + else if (networkType === "fantom") return `https://rpc.ankr.com/fantom`; else return `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}`; } @@ -67,6 +69,7 @@ function getBlockNumber(networkType: string) { else if (networkType === "polygon") return 25941254; else if (networkType === "arbitrum") return 7719792; else if (networkType === "optimism") return 4346343; + else if (networkType === "fantom") return 42070000; else return 14456907; } @@ -128,7 +131,13 @@ const config: HardhatUserConfig = { chainId: 10, accounts: [`0x${process.env.PRIVATE_KEY}`], gasPrice: 1000000, - } + }, + fantom_mainnet: { + url: `https://rpc.ankr.com/fantom`, + chainId: 250, + accounts: [`0x${process.env.PRIVATE_KEY}`], + gasPrice: 210011000000, + }, }, paths: { artifacts: "./artifacts", @@ -166,7 +175,7 @@ const config: HardhatUserConfig = { target: "ethers-v5", }, mocha: { - timeout: 10000 * 10000, + timeout: 10000000 * 1000, // 10M sec }, etherscan: { apiKey: `${process.env.SCAN_API_KEY}` diff --git a/package-lock.json b/package-lock.json index 5d28191c..f8df3f3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,8 @@ "@types/mocha": "^9.0.0", "hardhat": "^2.6.7", "hardhat-gas-reporter": "^1.0.4", + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-beta.19", "solidity-coverage": "^0.7.17", "ts-node": "^10.4.0" } diff --git a/package.json b/package.json index dee65610..674e3c14 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,8 @@ "@types/mocha": "^9.0.0", "hardhat": "^2.6.7", "hardhat-gas-reporter": "^1.0.4", + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-beta.19", "solidity-coverage": "^0.7.17", "ts-node": "^10.4.0" } diff --git a/scripts/run-tests.ts b/scripts/run-tests.ts index bd5901c5..85998887 100644 --- a/scripts/run-tests.ts +++ b/scripts/run-tests.ts @@ -12,7 +12,7 @@ async function testRunner() { name: "chain", message: "What chain do you want to run tests on?", type: "list", - choices: ["mainnet", "polygon", "avalanche", "arbitrum", "optimism"], + choices: ["mainnet", "polygon", "avalanche", "arbitrum", "optimism", "fantom"], }, ]); const testsPath = join(__dirname, "../test", chain); diff --git a/test/fantom/flashloan.ts b/test/fantom/flashloan.ts new file mode 100644 index 00000000..134bc174 --- /dev/null +++ b/test/fantom/flashloan.ts @@ -0,0 +1,179 @@ +const hre = require('hardhat') +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +const { ethers } = hre + +import { + InstaFlashAggregatorFantom, + InstaFlashAggregatorFantom__factory, + FlashAggregatorFantom__factory, + IERC20__factory, + IERC20, + InstaFlashReceiver__factory, + InstaFlashReceiver, + InstaFlashAggregatorProxy, + InstaFlashAggregatorProxy__factory, + AaveImplementationFantom, + AaveImplementationFantom__factory, + FLAImplementationFantom__factory, + FLAImplementationFantom, +} from '../../typechain' + +describe('FlashLoan', function () { + let Aggregator, + aggregator, + Receiver, + receiver: InstaFlashReceiver, + Proxy, + proxy: InstaFlashAggregatorProxy, + ImplAave, + implAave, + ImplFLA, + implFLA: FLAImplementationFantom, + proxyNew: any; + + let signer: SignerWithAddress + + const master = '0xa9061100d29C3C562a2e2421eb035741C1b42137' + let masterSigner: any + + let ABI = ['function initialize(address,address,address)'] + let iface = new ethers.utils.Interface(ABI) + + const DAI = '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E' + const USDC = '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75' + const ACC_DAI = '0x1c664Bafc646510684Ba1588798c67fe22a8c7cf' + const ACC_USDC = '0x1c664Bafc646510684Ba1588798c67fe22a8c7cf' + + const dai = ethers.utils.parseUnits('10', 18) + const usdc = ethers.utils.parseUnits('10', 6) + const Dai = ethers.utils.parseUnits('5000', 18) + const Usdc = ethers.utils.parseUnits('5000', 6) + + const zeroAddr = + '0x0000000000000000000000000000000000000000000000000000000000000000' + + let _instaData = '' + + + beforeEach(async function () { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + //@ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 36529195 + } + } + ] + }); + + ;[signer] = await ethers.getSigners() + Aggregator = new InstaFlashAggregatorFantom__factory(signer) + aggregator = await Aggregator.deploy() + await aggregator.deployed() + + ImplAave = new AaveImplementationFantom__factory(signer) + implAave = await ImplAave.deploy() + await implAave.deployed() + + ImplFLA = new FLAImplementationFantom__factory(signer) + implFLA = await ImplFLA.deploy() + await implFLA.deployed() + + const data = iface.encodeFunctionData('initialize', [signer.address, implAave.address, implFLA.address]) + + Proxy = new InstaFlashAggregatorProxy__factory(signer) + proxy = await Proxy.deploy(aggregator.address, master, data) + await proxy.deployed() + + Receiver = new InstaFlashReceiver__factory(signer) + receiver = await Receiver.deploy(proxy.address) + await receiver.deployed() + + proxyNew = new ethers.Contract( + proxy.address, + FlashAggregatorFantom__factory.abi, + ethers.provider, + ) + + const token_dai = new ethers.Contract( + DAI, + IERC20__factory.abi, + ethers.provider, + ) + + await hre.network.provider.send('hardhat_setBalance', [ + ACC_DAI, + ethers.utils.parseEther('10.0').toHexString(), + ]) + await hre.network.provider.send('hardhat_setBalance', [ + proxy.address, + ethers.utils.parseEther('10.0').toHexString(), + ]) + + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [ACC_DAI], + }) + + const signer_dai = await ethers.getSigner(ACC_DAI) + await token_dai.connect(signer_dai).transfer(receiver.address, dai) + await token_dai.connect(signer_dai).transfer(proxy.address, Dai) + + await hre.network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [ACC_DAI], + }) + + _instaData = '0x' + }) + + describe('Single token', async function () { + it('Should be able to take flashLoan of a single token from AAVE V3', async function () { + await receiver.flashBorrow([DAI], [Dai], 9, zeroAddr,_instaData) + }) + it('Should add new route and take flashloan', async function () { + // await proxyNew.connect(signer).addNewRoutesAndEnable(['10'],[implFLA.address]); + await receiver.flashBorrow([DAI], [Dai], 10, zeroAddr, _instaData); + }) + }) + + describe('Multi token', async function () { + beforeEach(async function () { + const token = new ethers.Contract( + USDC, + IERC20__factory.abi, + ethers.provider, + ) + + await hre.network.provider.send('hardhat_setBalance', [ + ACC_USDC, + ethers.utils.parseEther('10.0').toHexString(), + ]) + + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [ACC_USDC], + }) + + const signer_usdc = await ethers.getSigner(ACC_USDC) + await token.connect(signer_usdc).transfer(receiver.address, usdc) + await token.connect(signer_usdc).transfer(proxy.address, Usdc) + + await hre.network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [ACC_USDC], + }) + _instaData = '0x' + }) + it('Should be able to take flashLoan of multiple tokens together from AAVE V3', async function () { + await receiver.flashBorrow([DAI, USDC], [Dai, Usdc], 9, zeroAddr, _instaData ) + }) + it('Should add new route and take flashloan and take flashLoan of multiple tokens from FLA', async function () { + // await proxyNew.connect(signer).addNewRoutesAndEnable(['10'],[implFLA.address]); + await receiver.flashBorrow([DAI, USDC], [Dai, Usdc], 10, zeroAddr, _instaData ) + }) + }) +}) diff --git a/test/fantom/resolver.ts b/test/fantom/resolver.ts new file mode 100644 index 00000000..efb4d188 --- /dev/null +++ b/test/fantom/resolver.ts @@ -0,0 +1,125 @@ +const hre = require('hardhat') +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' +const { ethers } = hre + +import { + InstaFlashAggregatorFantom__factory, + InstaFlashAggregatorProxy__factory, + InstaFlashloanResolverFantom, + InstaFlashloanResolverFantom__factory, + AaveV3Resolver__factory, + AaveV3Resolver, + FLAImplementationFantom__factory, + FLAResolver, + FLAResolver__factory, + IERC20__factory, +} from '../../typechain' + +describe('Resolver', function () { + let AaveV3, aaveV3, FLA, fla; + let Resolver, resolver: InstaFlashloanResolverFantom + let signer: SignerWithAddress + + const proxy = "0x22ed23Cc6EFf065AfDb7D5fF0CBf6886fd19aee1"; + + const DAI = '0x8D11eC38a3EB5E956B052f67Da8Bdc9bef8Abf3E' + const USDC = '0x04068DA6C83AFCFA0e13ba15A6696662335D5B75' + + const Dai = ethers.utils.parseUnits('5000', 18) + const Usdc = ethers.utils.parseUnits('5000', 6) + + const ACC_DAI = '0x1c664Bafc646510684Ba1588798c67fe22a8c7cf' + const ACC_USDC = '0x1c664Bafc646510684Ba1588798c67fe22a8c7cf' + + const master = '0xa9061100d29C3C562a2e2421eb035741C1b42137' + + let ABI = ['function initialize(address,address,address)'] + let iface = new ethers.utils.Interface(ABI) + + beforeEach(async function () { + ;[signer] = await ethers.getSigners() + + AaveV3 = new AaveV3Resolver__factory(signer) + aaveV3 = await AaveV3.deploy() + await aaveV3.deployed() + // console.log('aaveV3 at: ', aaveV3.address) + + FLA = new FLAResolver__factory(signer) + fla = await FLA.deploy() + await fla.deployed() + // console.log('fla at: ', fla.address) + + Resolver = new InstaFlashloanResolverFantom__factory(signer) + resolver = await Resolver.deploy() + await resolver.deployed() + console.log("resolver deployed at: ", resolver.address) + + // await resolver.connect(signer).initialize(["9", "10"],[aaveV3.address, fla.address]) + + const token_dai = new ethers.Contract( + DAI, + IERC20__factory.abi, + ethers.provider, + ) + + await hre.network.provider.send('hardhat_setBalance', [ + ACC_DAI, + ethers.utils.parseEther('10.0').toHexString(), + ]) + await hre.network.provider.send('hardhat_setBalance', [ + proxy, + ethers.utils.parseEther('10.0').toHexString(), + ]) + + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [ACC_DAI], + }) + + const signer_dai = await ethers.getSigner(ACC_DAI) + await token_dai.connect(signer_dai).transfer(proxy, Dai) + + await hre.network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [ACC_DAI], + }) + + const token = new ethers.Contract( + USDC, + IERC20__factory.abi, + ethers.provider, + ) + + await hre.network.provider.send('hardhat_setBalance', [ + ACC_USDC, + ethers.utils.parseEther('10.0').toHexString(), + ]) + + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [ACC_USDC], + }) + + const signer_usdc = await ethers.getSigner(ACC_USDC) + await token.connect(signer_usdc).transfer(proxy, Usdc) + + await hre.network.provider.request({ + method: 'hardhat_stopImpersonatingAccount', + params: [ACC_USDC], + }) + }) + + it('Should be able to return routes info', async function () { + console.log((await resolver.getRoutesInfo()).toString()) + }) + + it('Should be able to return the best route for flashloan', async function () { + console.log( + (await resolver.getBestRoutes([DAI, USDC], [Dai, Usdc])).toString(), + ) + }) + + it('Should be able to return all the data for flashloan', async function () { + console.log((await resolver.getData([DAI, USDC], [Dai, Usdc])).toString()) + }) +})