Merge branch 'main' into ubiquity

This commit is contained in:
zapaz.eth 2021-12-12 21:20:08 +01:00
commit 4a7f1cc1d1
133 changed files with 30225 additions and 6702 deletions

27
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,27 @@
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.
#
# You can adjust the behavior by modifying this file.
# For more information, see:
# https://github.com/actions/stale
name: Mark stale issues and pull requests
on:
schedule:
- cron: '31 4 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'Stale issue message'
stale-pr-message: 'Stale pull request message'
stale-issue-label: 'no-issue-activity'
stale-pr-label: 'no-pr-activity'

1
.gitignore vendored
View File

@ -63,3 +63,4 @@ build/contracts
# buidler
artifacts
cache
typechain

View File

@ -8,6 +8,47 @@ You can create a PR to request a support for specific protocol or external contr
List of all the mainnet connector for referrence is [here](https://github.com/Instadapp/dsa-connectors/tree/main/contracts/mainnet/connectors)
## Usage
### Pre Requisites
Before running any command, make sure to install dependencies:
```sh
$ npm install
```
### Compile
Compile the smart contracts with Hardhat:
```sh
$ npm run compile
```
### TypeChain
Compile the smart contracts and generate TypeChain artifacts:
```sh
$ npm run typechain
```
### Test
Run tests using interactive CLI
```sh
$ npm run test:runner
```
Run all the tests:
```sh
$ npm run test
```
(Striclty use this envirnment to test, or otherwise make suitable changes in config file before testing).
## How to add a new connector
You can create a new PR to add a new connector. To get the PR merged, certain requirements needs to be met which will be explained here.

View File

@ -0,0 +1,10 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
contract Events {
event LogFlashBorrow(address token, uint256 tokenAmt);
event LogFlashPayback(address token, uint256 tokenAmt);
event LogFlashMultiBorrow(address[] token, uint256[] tokenAmts);
event LogFlashMultiPayback(address[] token, uint256[] tokenAmts);
}

View File

@ -0,0 +1,11 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
interface InstaFlashV4Interface {
function flashLoan(address[] memory tokens, uint256[] memory amts, uint route, bytes memory data, bytes memory extraData) external;
}
interface AccountInterface {
function enable(address) external;
function disable(address) external;
}

View File

@ -0,0 +1,138 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title Instapool.
* @dev Inbuilt Flash Loan in DSA
*/
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenInterface } from "../../common/interfaces.sol";
import { AccountInterface } from "./interfaces.sol";
import { Stores } from "../../common/stores.sol";
import { Variables } from "./variables.sol";
import { Events } from "./events.sol";
contract LiquidityResolver is Stores, Variables, Events {
using SafeERC20 for IERC20;
/**
* @dev Borrow Flashloan and Cast spells.
* @notice Borrow Flashloan and Cast spells.
* @param token Token Address.
* @param amt Token Amount.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashBorrowAndCast(
address token,
uint amt,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
address[] memory tokens_ = new address[](1);
tokens_[0] = token;
uint[] memory amts_ = new uint[](1);
amts_[0] = amt;
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashBorrow(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Return token to InstaPool.
* @notice Return token to InstaPool.
* @param token Token Address.
* @param amt Token Amount.
* @param getId Get token amount at this ID from `InstaMemory` Contract.
* @param setId Set token amount at this ID in `InstaMemory` Contract.
*/
function flashPayback(
address token,
uint amt,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
IERC20 tokenContract = IERC20(token);
tokenContract.safeTransfer(address(instaPool), _amt);
setUint(setId, _amt);
_eventName = "LogFlashPayback(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Borrow multi-tokens Flashloan and Cast spells.
* @notice Borrow multi-tokens Flashloan and Cast spells.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashMultiBorrowAndCast(
address[] memory tokens_,
uint[] memory amts_,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashMultiBorrow(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
/**
* @dev Return multi-tokens to InstaPool.
* @notice Return multi-tokens to InstaPool.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param getIds Array of getId token amounts.
* @param setIds Array of setId token amounts.
*/
function flashMultiPayback(
address[] memory tokens_,
uint[] memory amts_,
uint[] memory getIds,
uint[] memory setIds
) external payable returns (string memory _eventName, bytes memory _eventParam) {
for (uint i = 0; i < tokens_.length; i++) {
amts_[i] = getUint(getIds[i], amts_[i]);
IERC20(tokens_[i]).safeTransfer(address(instaPool), amts_[i]);
setUint(setIds[i], amts_[i]);
}
_eventName = "LogFlashMultiPayback(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
}
contract ConnectV2InstaPoolV4 is LiquidityResolver {
string public name = "Instapool-v4";
}

View File

@ -0,0 +1,13 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import { InstaFlashV4Interface } from "./interfaces.sol";
contract Variables {
/**
* @dev Instapool contract proxy
*/
InstaFlashV4Interface public constant instaPool = InstaFlashV4Interface(address(0)); // TODO: update address
}

View File

@ -0,0 +1,10 @@
pragma solidity ^0.7.0;
contract Events {
event LogSell(
uint24 fee,
uint256 amountIn,
uint256 amountOut,
uint256 amountOutMinimum
);
}

View File

@ -0,0 +1,123 @@
pragma solidity ^0.7.6;
pragma abicoder v2;
import {UniswapV3Pool, ISwapRouter} from "./interface.sol";
import {SqrtPriceMath} from "./libraries/SqrtPriceMath.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
contract Helpers {
using SafeERC20 for IERC20;
ISwapRouter internal constant router =
ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
bytes32 internal constant POOL_INIT_CODE_HASH =
0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
struct PoolKey {
address token0;
address token1;
uint24 fee;
}
function getPoolAddress(
address tokenA,
address tokenB,
uint24 fee
) internal pure returns (address pool) {
if (tokenA > tokenB) (tokenA, tokenB) = (tokenB, tokenA);
return
computeAddress(
0x1F98431c8aD98523631AE4a59f267346ea31F984,
PoolKey({token0: tokenA, token1: tokenB, fee: fee})
);
}
function computeAddress(address factory, PoolKey memory key)
internal
pure
returns (address pool)
{
require(key.token0 < key.token1);
pool = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"ff",
factory,
keccak256(
abi.encode(key.token0, key.token1, key.fee)
),
POOL_INIT_CODE_HASH
)
)
)
)
);
}
function getPriceLimit(
uint256 amountIn,
bool zeroForOne,
address tokenA,
address tokenB,
uint24 fee
) internal view returns (uint160) {
UniswapV3Pool state = UniswapV3Pool(
getPoolAddress(tokenA, tokenB, fee)
);
return (
SqrtPriceMath.getNextSqrtPriceFromInput(
state.slot0().sqrtPriceX96,
state.liquidity(),
amountIn,
zeroForOne
)
);
}
function getParams(
address tokenIn,
address tokenOut,
address recipient,
uint24 fee,
uint256 amountIn,
uint256 amountOutMinimum
) internal view returns (ISwapRouter.ExactInputSingleParams memory params) {
params = ISwapRouter.ExactInputSingleParams({
tokenIn: tokenIn,
tokenOut: tokenOut,
fee: fee,
recipient: recipient,
deadline: block.timestamp + 1,
amountIn: amountIn,
amountOutMinimum: amountOutMinimum,
sqrtPriceLimitX96: getPriceLimit(
amountIn,
tokenIn < tokenOut,
tokenIn,
tokenOut,
fee
)
});
}
function SwapTokens(
address tokenIn,
address tokenOut,
bool zeroForOne
) internal pure returns (address, address) {
if (!zeroForOne) return (tokenOut, tokenIn);
else return (tokenIn, tokenOut);
}
function swapSingleInput(ISwapRouter.ExactInputSingleParams memory params)
internal
returns (uint256)
{
return (uint256(router.exactInputSingle(params)));
}
}

View File

@ -0,0 +1,36 @@
pragma solidity ^0.7.6;
pragma abicoder v2;
interface UniswapV3Pool {
struct Slot0 {
uint160 sqrtPriceX96;
int24 tick;
uint16 observationIndex;
uint16 observationCardinality;
uint16 observationCardinalityNext;
uint8 feeProtocol;
bool unlocked;
}
function liquidity() external view returns (uint128);
function slot0() external view returns (Slot0 memory);
}
interface ISwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
returns (uint256);
}

View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.4.0;
/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000;
}

View File

@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = -denominator & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the precoditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}

View File

@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.0;
/// @title Optimized overflow and underflow safe math operations
/// @notice Contains methods for doing math operations that revert on overflow or underflow for minimal gas cost
library LowGasSafeMath {
/// @notice Returns x + y, reverts if sum overflows uint256
/// @param x The augend
/// @param y The addend
/// @return z The sum of x and y
function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x + y) >= x);
}
/// @notice Returns x - y, reverts if underflows
/// @param x The minuend
/// @param y The subtrahend
/// @return z The difference of x and y
function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
require((z = x - y) <= x);
}
/// @notice Returns x * y, reverts if overflows
/// @param x The multiplicand
/// @param y The multiplier
/// @return z The product of x and y
function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
require(x == 0 || (z = x * y) / x == y);
}
/// @notice Returns x + y, reverts if overflows or underflows
/// @param x The augend
/// @param y The addend
/// @return z The sum of x and y
function add(int256 x, int256 y) internal pure returns (int256 z) {
require((z = x + y) >= x == (y >= 0));
}
/// @notice Returns x - y, reverts if overflows or underflows
/// @param x The minuend
/// @param y The subtrahend
/// @return z The difference of x and y
function sub(int256 x, int256 y) internal pure returns (int256 z) {
require((z = x - y) <= x == (y >= 0));
}
}

View File

@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
/// @notice Cast a uint256 to a uint160, revert on overflow
/// @param y The uint256 to be downcasted
/// @return z The downcasted integer, now type uint160
function toUint160(uint256 y) internal pure returns (uint160 z) {
require((z = uint160(y)) == y);
}
/// @notice Cast a int256 to a int128, revert on overflow or underflow
/// @param y The int256 to be downcasted
/// @return z The downcasted integer, now type int128
function toInt128(int256 y) internal pure returns (int128 z) {
require((z = int128(y)) == y);
}
/// @notice Cast a uint256 to a int256, revert on overflow
/// @param y The uint256 to be casted
/// @return z The casted integer, now type int256
function toInt256(uint256 y) internal pure returns (int256 z) {
require(y < 2**255);
z = int256(y);
}
}

View File

@ -0,0 +1,227 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;
import './LowGasSafeMath.sol';
import './SafeCast.sol';
import './FullMath.sol';
import './UnsafeMath.sol';
import './FixedPoint96.sol';
/// @title Functions based on Q64.96 sqrt price and liquidity
/// @notice Contains the math that uses square root of price as a Q64.96 and liquidity to compute deltas
library SqrtPriceMath {
using LowGasSafeMath for uint256;
using SafeCast for uint256;
/// @notice Gets the next sqrt price given a delta of token0
/// @dev Always rounds up, because in the exact output case (increasing price) we need to move the price at least
/// far enough to get the desired output amount, and in the exact input case (decreasing price) we need to move the
/// price less in order to not send too much output.
/// The most precise formula for this is liquidity * sqrtPX96 / (liquidity +- amount * sqrtPX96),
/// if this is impossible because of overflow, we calculate liquidity / (liquidity / sqrtPX96 +- amount).
/// @param sqrtPX96 The starting price, i.e. before accounting for the token0 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of token0 to add or remove from virtual reserves
/// @param add Whether to add or remove the amount of token0
/// @return The price after adding or removing amount, depending on add
function getNextSqrtPriceFromAmount0RoundingUp(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amount,
bool add
) internal pure returns (uint160) {
// we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price
if (amount == 0) return sqrtPX96;
uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
if (add) {
uint256 product;
if ((product = amount * sqrtPX96) / amount == sqrtPX96) {
uint256 denominator = numerator1 + product;
if (denominator >= numerator1)
// always fits in 160 bits
return uint160(FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator));
}
return uint160(UnsafeMath.divRoundingUp(numerator1, (numerator1 / sqrtPX96).add(amount)));
} else {
uint256 product;
// if the product overflows, we know the denominator underflows
// in addition, we must check that the denominator does not underflow
require((product = amount * sqrtPX96) / amount == sqrtPX96 && numerator1 > product);
uint256 denominator = numerator1 - product;
return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator).toUint160();
}
}
/// @notice Gets the next sqrt price given a delta of token1
/// @dev Always rounds down, because in the exact output case (decreasing price) we need to move the price at least
/// far enough to get the desired output amount, and in the exact input case (increasing price) we need to move the
/// price less in order to not send too much output.
/// The formula we compute is within <1 wei of the lossless version: sqrtPX96 +- amount / liquidity
/// @param sqrtPX96 The starting price, i.e., before accounting for the token1 delta
/// @param liquidity The amount of usable liquidity
/// @param amount How much of token1 to add, or remove, from virtual reserves
/// @param add Whether to add, or remove, the amount of token1
/// @return The price after adding or removing `amount`
function getNextSqrtPriceFromAmount1RoundingDown(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amount,
bool add
) internal pure returns (uint160) {
// if we're adding (subtracting), rounding down requires rounding the quotient down (up)
// in both cases, avoid a mulDiv for most inputs
if (add) {
uint256 quotient =
(
amount <= type(uint160).max
? (amount << FixedPoint96.RESOLUTION) / liquidity
: FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity)
);
return uint256(sqrtPX96).add(quotient).toUint160();
} else {
uint256 quotient =
(
amount <= type(uint160).max
? UnsafeMath.divRoundingUp(amount << FixedPoint96.RESOLUTION, liquidity)
: FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity)
);
require(sqrtPX96 > quotient);
// always fits 160 bits
return uint160(sqrtPX96 - quotient);
}
}
/// @notice Gets the next sqrt price given an input amount of token0 or token1
/// @dev Throws if price or liquidity are 0, or if the next price is out of bounds
/// @param sqrtPX96 The starting price, i.e., before accounting for the input amount
/// @param liquidity The amount of usable liquidity
/// @param amountIn How much of token0, or token1, is being swapped in
/// @param zeroForOne Whether the amount in is token0 or token1
/// @return sqrtQX96 The price after adding the input amount to token0 or token1
function getNextSqrtPriceFromInput(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amountIn,
bool zeroForOne
) internal pure returns (uint160 sqrtQX96) {
require(sqrtPX96 > 0);
require(liquidity > 0);
// round to make sure that we don't pass the target price
return
zeroForOne
? getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountIn, true)
: getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountIn, true);
}
/// @notice Gets the next sqrt price given an output amount of token0 or token1
/// @dev Throws if price or liquidity are 0 or the next price is out of bounds
/// @param sqrtPX96 The starting price before accounting for the output amount
/// @param liquidity The amount of usable liquidity
/// @param amountOut How much of token0, or token1, is being swapped out
/// @param zeroForOne Whether the amount out is token0 or token1
/// @return sqrtQX96 The price after removing the output amount of token0 or token1
function getNextSqrtPriceFromOutput(
uint160 sqrtPX96,
uint128 liquidity,
uint256 amountOut,
bool zeroForOne
) internal pure returns (uint160 sqrtQX96) {
require(sqrtPX96 > 0);
require(liquidity > 0);
// round to make sure that we pass the target price
return
zeroForOne
? getNextSqrtPriceFromAmount1RoundingDown(sqrtPX96, liquidity, amountOut, false)
: getNextSqrtPriceFromAmount0RoundingUp(sqrtPX96, liquidity, amountOut, false);
}
/// @notice Gets the amount0 delta between two prices
/// @dev Calculates liquidity / sqrt(lower) - liquidity / sqrt(upper),
/// i.e. liquidity * (sqrt(upper) - sqrt(lower)) / (sqrt(upper) * sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The amount of usable liquidity
/// @param roundUp Whether to round the amount up or down
/// @return amount0 Amount of token0 required to cover a position of size liquidity between the two passed prices
function getAmount0Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount0) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
uint256 numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION;
uint256 numerator2 = sqrtRatioBX96 - sqrtRatioAX96;
require(sqrtRatioAX96 > 0);
return
roundUp
? UnsafeMath.divRoundingUp(
FullMath.mulDivRoundingUp(numerator1, numerator2, sqrtRatioBX96),
sqrtRatioAX96
)
: FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) / sqrtRatioAX96;
}
/// @notice Gets the amount1 delta between two prices
/// @dev Calculates liquidity * (sqrt(upper) - sqrt(lower))
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The amount of usable liquidity
/// @param roundUp Whether to round the amount up, or down
/// @return amount1 Amount of token1 required to cover a position of size liquidity between the two passed prices
function getAmount1Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
uint128 liquidity,
bool roundUp
) internal pure returns (uint256 amount1) {
if (sqrtRatioAX96 > sqrtRatioBX96) (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96);
return
roundUp
? FullMath.mulDivRoundingUp(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96)
: FullMath.mulDiv(liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96);
}
/// @notice Helper that gets signed token0 delta
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The change in liquidity for which to compute the amount0 delta
/// @return amount0 Amount of token0 corresponding to the passed liquidityDelta between the two prices
function getAmount0Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
int128 liquidity
) internal pure returns (int256 amount0) {
return
liquidity < 0
? -getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
: getAmount0Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
}
/// @notice Helper that gets signed token1 delta
/// @param sqrtRatioAX96 A sqrt price
/// @param sqrtRatioBX96 Another sqrt price
/// @param liquidity The change in liquidity for which to compute the amount1 delta
/// @return amount1 Amount of token1 corresponding to the passed liquidityDelta between the two prices
function getAmount1Delta(
uint160 sqrtRatioAX96,
uint160 sqrtRatioBX96,
int128 liquidity
) internal pure returns (int256 amount1) {
return
liquidity < 0
? -getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), false).toInt256()
: getAmount1Delta(sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), true).toInt256();
}
}

View File

@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Math functions that do not check inputs or outputs
/// @notice Contains methods that perform common math functions but do not do any overflow or underflow checks
library UnsafeMath {
/// @notice Returns ceil(x / y)
/// @dev division by 0 has unspecified behavior, and must be checked externally
/// @param x The dividend
/// @param y The divisor
/// @return z The quotient, ceil(x / y)
function divRoundingUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
assembly {
z := add(div(x, y), gt(mod(x, y), 0))
}
}
}

View File

@ -0,0 +1,41 @@
pragma solidity ^0.7.6;
pragma abicoder v2;
import "./helpers.sol";
import {Events} from "./events.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
contract uniswapSellBeta is Helpers, Events {
using SafeERC20 for IERC20;
function sell(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountIn,
uint256 amountOutMinimum
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
IERC20(tokenIn).safeApprove(address(router), amountIn);
uint256 amountOut = swapSingleInput(
getParams(
tokenIn,
tokenOut,
address(this),
fee,
amountIn,
amountOutMinimum
)
);
_eventName = "LogSell(uint24,uint256,uint256,uint256)";
_eventParam = abi.encode(fee, amountIn, amountOut, amountOutMinimum);
}
}
contract ConnectV2UniswapSellBeta is uniswapSellBeta {
string public constant name = "Uniswap-Sell-Beta";
}

View File

@ -0,0 +1,6 @@
pragma solidity ^0.7.0;
contract Events {
event LogClaimedReward(uint256 rewardAmt, uint256 setId);
event LogDelegate(address delegatee);
}

View File

@ -0,0 +1,59 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import { DSMath } from "../../../common/math.sol";
import { Basic } from "../../../common/basic.sol";
import { ComptrollerInterface, QiInterface, BenqiMappingInterface } from "./interface.sol";
abstract contract Helpers is DSMath, Basic {
/**
* @dev Benqi Comptroller
*/
ComptrollerInterface internal constant troller = ComptrollerInterface(0x486Af39519B4Dc9a7fCcd318217352830E8AD9b4);
/**
* @dev Qi Reward Token
*/
QiInterface internal constant benqiToken = QiInterface(0x8729438EB15e2C8B576fCc6AeCdA6A148776C0F5);
/**
* @dev Benqi Mapping
*/
BenqiMappingInterface internal constant qiMapping = BenqiMappingInterface(0xe19Fba29ac9BAACc1F584aEcD9C98B4F6fC58ba6);
/**
* @dev Benqi reward token type to show BENQI or AVAX
*/
uint8 internal constant rewardQi = 0;
uint8 internal constant rewardAvax = 1;
function getMergedQiTokens(
string[] calldata supplyIds,
string[] calldata borrowIds
) internal view returns (address[] memory qitokens, bool isBorrow, bool isSupply) {
uint _supplyLen = supplyIds.length;
uint _borrowLen = borrowIds.length;
uint _totalLen = add(_supplyLen, _borrowLen);
qitokens = new address[](_totalLen);
if(_supplyLen > 0) {
isSupply = true;
for (uint i = 0; i < _supplyLen; i++) {
(address token, address qiToken) = qiMapping.getMapping(supplyIds[i]);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
qitokens[i] = qiToken;
}
}
if(_borrowLen > 0) {
isBorrow = true;
for (uint i = 0; i < _borrowLen; i++) {
(address token, address qiToken) = qiMapping.getMapping(borrowIds[i]);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
qitokens[_supplyLen + i] = qiToken;
}
}
}
}

View File

@ -0,0 +1,17 @@
pragma solidity ^0.7.0;
interface ComptrollerInterface {
function claimReward(uint8 rewardType, address holder) external;
function claimReward(uint8 rewardType, address holder, address[] calldata) external;
function claimReward(uint8 rewardType, address[] calldata holders, address[] calldata qiTokens, bool borrowers, bool suppliers) external;
}
interface QiInterface {
function delegate(address delegatee) external;
function delegates(address) external view returns(address);
}
interface BenqiMappingInterface {
function qiTokenMapping(string calldata tokenId) external view returns (address);
function getMapping(string calldata tokenId) external view returns (address, address);
}

View File

@ -0,0 +1,171 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title Reward.
* @dev Claim Reward.
*/
import { TokenInterface } from "../../../common/interfaces.sol";
import { Stores } from "../../../common/stores.sol";
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
abstract contract IncentivesResolver is Events, Helpers {
/**
* @dev Claim Accrued Qi Token.
* @notice Claim Accrued Qi Token.
* @param setId ID stores the amount of Reward claimed.
*/
function ClaimQiReward(uint256 setId) external payable returns (string memory _eventName, bytes memory _eventParam) {
TokenInterface _benqiToken = TokenInterface(address(benqiToken));
uint intialBal = _benqiToken.balanceOf(address(this));
troller.claimReward(rewardQi, address(this));
uint finalBal = _benqiToken.balanceOf(address(this));
uint amt = sub(finalBal, intialBal);
setUint(setId, amt);
_eventName = "LogClaimedReward(uint256,uint256)";
_eventParam = abi.encode(amt, setId);
}
/**
* @dev Claim Accrued Qi Token.
* @notice Claim Accrued Qi Token.
* @param tokenIds Array of supplied and borrowed token IDs.
* @param setId ID stores the amount of Reward claimed.
*/
function ClaimQiRewardTwo(string[] calldata tokenIds, uint256 setId) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint _len = tokenIds.length;
address[] memory qitokens = new address[](_len);
for (uint i = 0; i < _len; i++) {
(address token, address qiToken) = qiMapping.getMapping(tokenIds[i]);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
qitokens[i] = qiToken;
}
TokenInterface _benqiToken = TokenInterface(address(benqiToken));
uint intialBal = _benqiToken.balanceOf(address(this));
troller.claimReward(rewardQi, address(this), qitokens);
uint finalBal = _benqiToken.balanceOf(address(this));
uint amt = sub(finalBal, intialBal);
setUint(setId, amt);
_eventName = "LogClaimedReward(uint256,uint256)";
_eventParam = abi.encode(amt, setId);
}
/**
* @dev Claim Accrued Qi Token.
* @notice Claim Accrued Qi Token.
* @param supplyTokenIds Array of supplied tokenIds.
* @param borrowTokenIds Array of borrowed tokenIds.
* @param setId ID stores the amount of Reward claimed.
*/
function ClaimQiRewardThree(string[] calldata supplyTokenIds, string[] calldata borrowTokenIds, uint256 setId) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address[] memory qitokens, bool isBorrow, bool isSupply) = getMergedQiTokens(supplyTokenIds, borrowTokenIds);
address[] memory holders = new address[](1);
holders[0] = address(this);
TokenInterface _benqiToken = TokenInterface(address(benqiToken));
uint intialBal = _benqiToken.balanceOf(address(this));
troller.claimReward(rewardQi, holders, qitokens, isBorrow, isSupply);
uint finalBal = _benqiToken.balanceOf(address(this));
uint amt = sub(finalBal, intialBal);
setUint(setId, amt);
_eventName = "LogClaimedReward(uint256,uint256)";
_eventParam = abi.encode(amt, setId);
}
/**
* @dev Claim Accrued Avax Token.
* @notice Claim Accrued Avax Token.
* @param setId ID stores the amount of Reward claimed.
*/
function ClaimAvaxReward(uint256 setId) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint intialBal = address(this).balance;
troller.claimReward(rewardAvax, address(this));
uint finalBal = address(this).balance;
uint amt = sub(finalBal, intialBal);
setUint(setId, amt);
_eventName = "LogClaimedReward(uint256,uint256)";
_eventParam = abi.encode(amt, setId);
}
/**
* @dev Claim Accrued Avax Token.
* @notice Claim Accrued Avax Token.
* @param tokenIds Array of supplied and borrowed token IDs.
* @param setId ID stores the amount of Reward claimed.
*/
function ClaimAvaxRewardTwo(string[] calldata tokenIds, uint256 setId) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint _len = tokenIds.length;
address[] memory qitokens = new address[](_len);
for (uint i = 0; i < _len; i++) {
(address token, address qiToken) = qiMapping.getMapping(tokenIds[i]);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
qitokens[i] = qiToken;
}
uint intialBal = address(this).balance;
troller.claimReward(rewardAvax, address(this), qitokens);
uint finalBal = address(this).balance;
uint amt = sub(finalBal, intialBal);
setUint(setId, amt);
_eventName = "LogClaimedReward(uint256,uint256)";
_eventParam = abi.encode(amt, setId);
}
/**
* @dev Claim Accrued Avax Token.
* @notice Claim Accrued Avax Token.
* @param supplyTokenIds Array of supplied tokenIds.
* @param borrowTokenIds Array of borrowed tokenIds.
* @param setId ID stores the amount of Reward claimed.
*/
function ClaimAvaxRewardThree(string[] calldata supplyTokenIds, string[] calldata borrowTokenIds, uint256 setId) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address[] memory qitokens, bool isBorrow, bool isSupply) = getMergedQiTokens(supplyTokenIds, borrowTokenIds);
address[] memory holders = new address[](1);
holders[0] = address(this);
uint intialBal = address(this).balance;
troller.claimReward(rewardAvax, holders, qitokens, isBorrow, isSupply);
uint finalBal = address(this).balance;
uint amt = sub(finalBal, intialBal);
setUint(setId, amt);
_eventName = "LogClaimedReward(uint256,uint256)";
_eventParam = abi.encode(amt, setId);
}
/**
* @dev Delegate votes.
* @notice Delegate votes.
* @param delegatee The address to delegate the votes to.
*/
function delegate(address delegatee) external payable returns (string memory _eventName, bytes memory _eventParam) {
require(benqiToken.delegates(address(this)) != delegatee, "Already delegated to same delegatee.");
benqiToken.delegate(delegatee);
_eventName = "LogDelegate(address)";
_eventParam = abi.encode(delegatee);
}
}
contract ConnectV2BenqiIncentivesAvalanche is IncentivesResolver {
string public constant name = "Benqi-Incentives-v1";
}

View File

@ -0,0 +1,62 @@
pragma solidity ^0.7.0;
contract Events {
event LogDeposit(
address indexed token,
address qiToken,
uint256 tokenAmt,
uint256 getId,
uint256 setId
);
event LogWithdraw(
address indexed token,
address qiToken,
uint256 tokenAmt,
uint256 getId,
uint256 setId
);
event LogBorrow(
address indexed token,
address qiToken,
uint256 tokenAmt,
uint256 getId,
uint256 setId
);
event LogPayback(
address indexed token,
address qiToken,
uint256 tokenAmt,
uint256 getId,
uint256 setId
);
event LogDepositQiToken(
address indexed token,
address qiToken,
uint256 tokenAmt,
uint256 qiTokenAmt,
uint256 getId,
uint256 setId
);
event LogWithdrawQiToken(
address indexed token,
address qiToken,
uint256 tokenAmt,
uint256 qiTokenAmt,
uint256 getId,
uint256 setId
);
event LogLiquidate(
address indexed borrower,
address indexed tokenToPay,
address indexed tokenInReturn,
uint256 tokenAmt,
uint256 getId,
uint256 setId
);
}

View File

@ -0,0 +1,35 @@
pragma solidity ^0.7.0;
import { DSMath } from "../../../common/math.sol";
import { Basic } from "../../../common/basic.sol";
import { ComptrollerInterface, BenqiMappingInterface } from "./interface.sol";
abstract contract Helpers is DSMath, Basic {
/**
* @dev Benqi Comptroller
*/
ComptrollerInterface internal constant troller = ComptrollerInterface(0x486Af39519B4Dc9a7fCcd318217352830E8AD9b4);
/**
* @dev Benqi Mapping
*/
BenqiMappingInterface internal constant qiMapping = BenqiMappingInterface(0xe19Fba29ac9BAACc1F584aEcD9C98B4F6fC58ba6);
/**
* @dev enter benqi market
*/
function enterMarket(address qiToken) internal {
address[] memory markets = troller.getAssetsIn(address(this));
bool isEntered = false;
for (uint i = 0; i < markets.length; i++) {
if (markets[i] == qiToken) {
isEntered = true;
}
}
if (!isEntered) {
address[] memory toEnter = new address[](1);
toEnter[0] = qiToken;
troller.enterMarkets(toEnter);
}
}
}

View File

@ -0,0 +1,36 @@
pragma solidity ^0.7.0;
interface QiTokenInterface {
function mint(uint mintAmount) external returns (uint);
function redeem(uint redeemTokens) external returns (uint);
function borrow(uint borrowAmount) external returns (uint);
function repayBorrow(uint repayAmount) external returns (uint);
function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint); // For ARC20
function liquidateBorrow(address borrower, uint repayAmount, address qiTokenCollateral) external returns (uint);
function borrowBalanceCurrent(address account) external returns (uint);
function redeemUnderlying(uint redeemAmount) external returns (uint);
function exchangeRateCurrent() external returns (uint);
function balanceOf(address owner) external view returns (uint256 balance);
}
interface QiAVAXInterface {
function mint() external payable;
function repayBorrow() external payable;
function repayBorrowBehalf(address borrower) external payable;
function liquidateBorrow(address borrower, address qiTokenCollateral) external payable;
}
interface ComptrollerInterface {
function enterMarkets(address[] calldata qiTokens) external returns (uint[] memory);
function exitMarket(address qiTokenAddress) external returns (uint);
function getAssetsIn(address account) external view returns (address[] memory);
function getAccountLiquidity(address account) external view returns (uint, uint, uint);
function claimReward(uint8 rewardType, address) external;
}
interface BenqiMappingInterface {
function qiTokenMapping(string calldata tokenId) external view returns (address);
function getMapping(string calldata tokenId) external view returns (address, address);
}

View File

@ -0,0 +1,441 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title Benqi.
* @dev Lending & Borrowing.
*/
import { TokenInterface } from "../../../common/interfaces.sol";
import { Stores } from "../../../common/stores.sol";
import { Helpers } from "./helpers.sol";
import { Events } from "./events.sol";
import { QiAVAXInterface, QiTokenInterface } from "./interface.sol";
abstract contract BenqiResolver is Events, Helpers {
/**
* @dev Deposit AVAX/ARC20_Token.
* @notice Deposit a token to Benqi for lending / collaterization.
* @param token The address of the token to deposit. (For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param qiToken The address of the corresponding qiToken.
* @param amt The amount of the token to deposit. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens deposited.
*/
function depositRaw(
address token,
address qiToken,
uint256 amt,
uint256 getId,
uint256 setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
enterMarket(qiToken);
if (token == avaxAddr) {
_amt = _amt == uint(-1) ? address(this).balance : _amt;
QiAVAXInterface(qiToken).mint{value: _amt}();
} else {
TokenInterface tokenContract = TokenInterface(token);
_amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt;
approve(tokenContract, qiToken, _amt);
require(QiTokenInterface(qiToken).mint(_amt) == 0, "deposit-failed");
}
setUint(setId, _amt);
_eventName = "LogDeposit(address,address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, qiToken, _amt, getId, setId);
}
/**
* @dev Deposit AVAX/ARC20_Token using the Mapping.
* @notice Deposit a token to Benqi for lending / collaterization.
* @param tokenId The token id of the token to deposit.(For eg: AVAX-A)
* @param amt The amount of the token to deposit. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens deposited.
*/
function deposit(
string calldata tokenId,
uint256 amt,
uint256 getId,
uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address token, address qiToken) = qiMapping.getMapping(tokenId);
(_eventName, _eventParam) = depositRaw(token, qiToken, amt, getId, setId);
}
/**
* @dev Withdraw AVAX/ARC20_Token.
* @notice Withdraw deposited token from Benqi
* @param token The address of the token to withdraw. (For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param qiToken The address of the corresponding qiToken.
* @param amt The amount of the token to withdraw. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens withdrawn.
*/
function withdrawRaw(
address token,
address qiToken,
uint256 amt,
uint256 getId,
uint256 setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
QiTokenInterface qiTokenContract = QiTokenInterface(qiToken);
if (_amt == uint(-1)) {
TokenInterface tokenContract = TokenInterface(token);
uint initialBal = token == avaxAddr ? address(this).balance : tokenContract.balanceOf(address(this));
require(qiTokenContract.redeem(qiTokenContract.balanceOf(address(this))) == 0, "full-withdraw-failed");
uint finalBal = token == avaxAddr ? address(this).balance : tokenContract.balanceOf(address(this));
_amt = finalBal - initialBal;
} else {
require(qiTokenContract.redeemUnderlying(_amt) == 0, "withdraw-failed");
}
setUint(setId, _amt);
_eventName = "LogWithdraw(address,address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, qiToken, _amt, getId, setId);
}
/**
* @dev Withdraw AVAX/ARC20_Token using the Mapping.
* @notice Withdraw deposited token from Benqi
* @param tokenId The token id of the token to withdraw.(For eg: AVAX-A)
* @param amt The amount of the token to withdraw. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens withdrawn.
*/
function withdraw(
string calldata tokenId,
uint256 amt,
uint256 getId,
uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address token, address qiToken) = qiMapping.getMapping(tokenId);
(_eventName, _eventParam) = withdrawRaw(token, qiToken, amt, getId, setId);
}
/**
* @dev Borrow AVAX/ARC20_Token.
* @notice Borrow a token using Benqi
* @param token The address of the token to borrow. (For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param qiToken The address of the corresponding qiToken.
* @param amt The amount of the token to borrow.
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens borrowed.
*/
function borrowRaw(
address token,
address qiToken,
uint256 amt,
uint256 getId,
uint256 setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
enterMarket(qiToken);
require(QiTokenInterface(qiToken).borrow(_amt) == 0, "borrow-failed");
setUint(setId, _amt);
_eventName = "LogBorrow(address,address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, qiToken, _amt, getId, setId);
}
/**
* @dev Borrow AVAX/ARC20_Token using the Mapping.
* @notice Borrow a token using Benqi
* @param tokenId The token id of the token to borrow.(For eg: DAI-A)
* @param amt The amount of the token to borrow.
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens borrowed.
*/
function borrow(
string calldata tokenId,
uint256 amt,
uint256 getId,
uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address token, address qiToken) = qiMapping.getMapping(tokenId);
(_eventName, _eventParam) = borrowRaw(token, qiToken, amt, getId, setId);
}
/**
* @dev Payback borrowed AVAX/ARC20_Token.
* @notice Payback debt owed.
* @param token The address of the token to payback. (For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param qiToken The address of the corresponding qiToken.
* @param amt The amount of the token to payback. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens paid back.
*/
function paybackRaw(
address token,
address qiToken,
uint256 amt,
uint256 getId,
uint256 setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
QiTokenInterface qiTokenContract = QiTokenInterface(qiToken);
_amt = _amt == uint(-1) ? qiTokenContract.borrowBalanceCurrent(address(this)) : _amt;
if (token == avaxAddr) {
require(address(this).balance >= _amt, "not-enough-avax");
QiAVAXInterface(qiToken).repayBorrow{value: _amt}();
} else {
TokenInterface tokenContract = TokenInterface(token);
require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token");
approve(tokenContract, qiToken, _amt);
require(qiTokenContract.repayBorrow(_amt) == 0, "repay-failed.");
}
setUint(setId, _amt);
_eventName = "LogPayback(address,address,uint256,uint256,uint256)";
_eventParam = abi.encode(token, qiToken, _amt, getId, setId);
}
/**
* @dev Payback borrowed AVAX/ARC20_Token using the Mapping.
* @notice Payback debt owed.
* @param tokenId The token id of the token to payback.(For eg: BENQI-A)
* @param amt The amount of the token to payback. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of tokens paid back.
*/
function payback(
string calldata tokenId,
uint256 amt,
uint256 getId,
uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address token, address qiToken) = qiMapping.getMapping(tokenId);
(_eventName, _eventParam) = paybackRaw(token, qiToken, amt, getId, setId);
}
/**
* @dev Deposit AVAX/ARC20_Token.
* @notice Same as depositRaw. The only difference is this method stores qiToken amount in set ID.
* @param token The address of the token to deposit. (For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param qiToken The address of the corresponding qiToken.
* @param amt The amount of the token to deposit. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of qiTokens received.
*/
function depositQiTokenRaw(
address token,
address qiToken,
uint256 amt,
uint256 getId,
uint256 setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
enterMarket(qiToken);
QiTokenInterface qitokenContract = QiTokenInterface(qiToken);
uint initialBal = qitokenContract.balanceOf(address(this));
if (token == avaxAddr) {
_amt = _amt == uint(-1) ? address(this).balance : _amt;
QiAVAXInterface(qiToken).mint{value: _amt}();
} else {
TokenInterface tokenContract = TokenInterface(token);
_amt = _amt == uint(-1) ? tokenContract.balanceOf(address(this)) : _amt;
approve(tokenContract, qiToken, _amt);
require(qitokenContract.mint(_amt) == 0, "deposit-qitoken-failed.");
}
uint _cAmt;
{
uint finalBal = qitokenContract.balanceOf(address(this));
_cAmt = sub(finalBal, initialBal);
setUint(setId, _cAmt);
}
_eventName = "LogDepositQiToken(address,address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(token, qiToken, _amt, _cAmt, getId, setId);
}
/**
* @dev Deposit AVAX/ARC20_Token using the Mapping.
* @notice Same as deposit. The only difference is this method stores qiToken amount in set ID.
* @param tokenId The token id of the token to depositQiToken.(For eg: DAI-A)
* @param amt The amount of the token to deposit. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of qiTokens received.
*/
function depositQiToken(
string calldata tokenId,
uint256 amt,
uint256 getId,
uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address token, address qiToken) = qiMapping.getMapping(tokenId);
(_eventName, _eventParam) = depositQiTokenRaw(token, qiToken, amt, getId, setId);
}
/**
* @dev Withdraw QiAVAX/QiARC20_Token using qiToken Amt.
* @notice Same as withdrawRaw. The only difference is this method fetch qiToken amount in get ID.
* @param token The address of the token to withdraw. (For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param qiToken The address of the corresponding qiToken.
* @param qiTokenAmt The amount of qiTokens to withdraw
* @param getId ID to retrieve qiTokenAmt
* @param setId ID stores the amount of tokens withdrawn.
*/
function withdrawQiTokenRaw(
address token,
address qiToken,
uint qiTokenAmt,
uint getId,
uint setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _cAmt = getUint(getId, qiTokenAmt);
require(token != address(0) && qiToken != address(0), "invalid token/qitoken address");
QiTokenInterface qiTokenContract = QiTokenInterface(qiToken);
TokenInterface tokenContract = TokenInterface(token);
_cAmt = _cAmt == uint(-1) ? qiTokenContract.balanceOf(address(this)) : _cAmt;
uint withdrawAmt;
{
uint initialBal = token != avaxAddr ? tokenContract.balanceOf(address(this)) : address(this).balance;
require(qiTokenContract.redeem(_cAmt) == 0, "redeem-failed");
uint finalBal = token != avaxAddr ? tokenContract.balanceOf(address(this)) : address(this).balance;
withdrawAmt = sub(finalBal, initialBal);
}
setUint(setId, withdrawAmt);
_eventName = "LogWithdrawQiToken(address,address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(token, qiToken, withdrawAmt, _cAmt, getId, setId);
}
/**
* @dev Withdraw QiAVAX/QiARC20_Token using qiToken Amt & the Mapping.
* @notice Same as withdraw. The only difference is this method fetch qiToken amount in get ID.
* @param tokenId The token id of the token to withdraw QiToken.(For eg: AVAX-A)
* @param qiTokenAmt The amount of qiTokens to withdraw
* @param getId ID to retrieve qiTokenAmt
* @param setId ID stores the amount of tokens withdrawn.
*/
function withdrawQiToken(
string calldata tokenId,
uint qiTokenAmt,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address token, address qiToken) = qiMapping.getMapping(tokenId);
(_eventName, _eventParam) = withdrawQiTokenRaw(token, qiToken, qiTokenAmt, getId, setId);
}
/**
* @dev Liquidate a position.
* @notice Liquidate a position.
* @param borrower Borrower's Address.
* @param tokenToPay The address of the token to pay for liquidation.(For AVAX: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)
* @param qiTokenPay Corresponding qiToken address.
* @param tokenInReturn The address of the token to return for liquidation.
* @param qiTokenColl Corresponding qiToken address.
* @param amt The token amount to pay for liquidation.
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of paid for liquidation.
*/
function liquidateRaw(
address borrower,
address tokenToPay,
address qiTokenPay,
address tokenInReturn,
address qiTokenColl,
uint256 amt,
uint256 getId,
uint256 setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
require(tokenToPay != address(0) && qiTokenPay != address(0), "invalid token/qitoken address");
require(tokenInReturn != address(0) && qiTokenColl != address(0), "invalid token/qitoken address");
QiTokenInterface qiTokenContract = QiTokenInterface(qiTokenPay);
{
(,, uint shortfal) = troller.getAccountLiquidity(borrower);
require(shortfal != 0, "account-cannot-be-liquidated");
_amt = _amt == uint(-1) ? qiTokenContract.borrowBalanceCurrent(borrower) : _amt;
}
if (tokenToPay == avaxAddr) {
require(address(this).balance >= _amt, "not-enough-avax");
QiAVAXInterface(qiTokenPay).liquidateBorrow{value: _amt}(borrower, qiTokenColl);
} else {
TokenInterface tokenContract = TokenInterface(tokenToPay);
require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token");
approve(tokenContract, qiTokenPay, _amt);
require(qiTokenContract.liquidateBorrow(borrower, _amt, qiTokenColl) == 0, "liquidate-failed");
}
setUint(setId, _amt);
_eventName = "LogLiquidate(address,address,address,uint256,uint256,uint256)";
_eventParam = abi.encode(
address(this),
tokenToPay,
tokenInReturn,
_amt,
getId,
setId
);
}
/**
* @dev Liquidate a position using the mapping.
* @notice Liquidate a position using the mapping.
* @param borrower Borrower's Address.
* @param tokenIdToPay token id of the token to pay for liquidation.(For eg: AVAX-A)
* @param tokenIdInReturn token id of the token to return for liquidation.(For eg: USDC-A)
* @param amt token amount to pay for liquidation.
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of paid for liquidation.
*/
function liquidate(
address borrower,
string calldata tokenIdToPay,
string calldata tokenIdInReturn,
uint256 amt,
uint256 getId,
uint256 setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
(address tokenToPay, address qiTokenToPay) = qiMapping.getMapping(tokenIdToPay);
(address tokenInReturn, address qiTokenColl) = qiMapping.getMapping(tokenIdInReturn);
(_eventName, _eventParam) = liquidateRaw(
borrower,
tokenToPay,
qiTokenToPay,
tokenInReturn,
qiTokenColl,
amt,
getId,
setId
);
}
}
contract ConnectV2BenqiAvalanche is BenqiResolver {
string public constant name = "Benqi-v1";
}

View File

@ -0,0 +1,10 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
contract Events {
event LogFlashBorrow(address token, uint256 tokenAmt);
event LogFlashPayback(address token, uint256 tokenAmt);
event LogFlashMultiBorrow(address[] token, uint256[] tokenAmts);
event LogFlashMultiPayback(address[] token, uint256[] tokenAmts);
}

View File

@ -0,0 +1,11 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
interface InstaFlashV4Interface {
function flashLoan(address[] memory tokens, uint256[] memory amts, uint route, bytes memory data, bytes memory extraData) external;
}
interface AccountInterface {
function enable(address) external;
function disable(address) external;
}

View File

@ -0,0 +1,138 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title Instapool.
* @dev Inbuilt Flash Loan in DSA
*/
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenInterface } from "../../common/interfaces.sol";
import { AccountInterface } from "./interfaces.sol";
import { Stores } from "../../common/stores.sol";
import { Variables } from "./variables.sol";
import { Events } from "./events.sol";
contract LiquidityResolver is Stores, Variables, Events {
using SafeERC20 for IERC20;
/**
* @dev Borrow Flashloan and Cast spells.
* @notice Borrow Flashloan and Cast spells.
* @param token Token Address.
* @param amt Token Amount.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashBorrowAndCast(
address token,
uint amt,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
address[] memory tokens_ = new address[](1);
tokens_[0] = token;
uint[] memory amts_ = new uint[](1);
amts_[0] = amt;
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashBorrow(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Return token to InstaPool.
* @notice Return token to InstaPool.
* @param token Token Address.
* @param amt Token Amount.
* @param getId Get token amount at this ID from `InstaMemory` Contract.
* @param setId Set token amount at this ID in `InstaMemory` Contract.
*/
function flashPayback(
address token,
uint amt,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
IERC20 tokenContract = IERC20(token);
tokenContract.safeTransfer(address(instaPool), _amt);
setUint(setId, _amt);
_eventName = "LogFlashPayback(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Borrow multi-tokens Flashloan and Cast spells.
* @notice Borrow multi-tokens Flashloan and Cast spells.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashMultiBorrowAndCast(
address[] memory tokens_,
uint[] memory amts_,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashMultiBorrow(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
/**
* @dev Return multi-tokens to InstaPool.
* @notice Return multi-tokens to InstaPool.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param getIds Array of getId token amounts.
* @param setIds Array of setId token amounts.
*/
function flashMultiPayback(
address[] memory tokens_,
uint[] memory amts_,
uint[] memory getIds,
uint[] memory setIds
) external payable returns (string memory _eventName, bytes memory _eventParam) {
for (uint i = 0; i < tokens_.length; i++) {
amts_[i] = getUint(getIds[i], amts_[i]);
IERC20(tokens_[i]).safeTransfer(address(instaPool), amts_[i]);
setUint(setIds[i], amts_[i]);
}
_eventName = "LogFlashMultiPayback(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
}
contract ConnectV2InstaPoolV4 is LiquidityResolver {
string public name = "Instapool-v4";
}

View File

@ -0,0 +1,13 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import { InstaFlashV4Interface } from "./interfaces.sol";
contract Variables {
/**
* @dev Instapool contract proxy
*/
InstaFlashV4Interface public constant instaPool = InstaFlashV4Interface(address(0)); // TODO: update address
}

View File

@ -0,0 +1,148 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
interface IndexInterface {
function master() external view returns (address);
}
interface ConnectorsInterface {
function chief(address) external view returns (bool);
}
interface QiTokenInterface {
function isQiToken() external view returns (bool);
function underlying() external view returns (address);
}
interface MappingControllerInterface {
function hasRole(address,address) external view returns (bool);
}
abstract contract Helpers {
struct TokenMap {
address qitoken;
address token;
}
event LogQiTokenAdded(string indexed name, address indexed token, address indexed qitoken);
event LogQiTokenUpdated(string indexed name, address indexed token, address indexed qitoken);
// InstaConnectorsV2
ConnectorsInterface public constant connectors = ConnectorsInterface(0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14);
// InstaIndex Address.
IndexInterface public constant instaIndex = IndexInterface(0x6CE3e607C808b4f4C26B7F6aDAeB619e49CAbb25);
// InstaMappingController Address.
MappingControllerInterface public constant mappingController = MappingControllerInterface(0xF2113d0c99f36D7D6F6c6FAf05E0863892255999);
address public constant avaxAddr = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
mapping (string => TokenMap) public qiTokenMapping;
modifier isChief {
require(msg.sender == instaIndex.master() || connectors.chief(msg.sender), "not-an-chief");
_;
}
modifier hasRoleOrIsChief {
require(
msg.sender == instaIndex.master() ||
connectors.chief(msg.sender) ||
mappingController.hasRole(address(this), msg.sender),
"not-an-chief/controller"
);
_;
}
function _addQitokenMapping(
string[] memory _names,
address[] memory _tokens,
address[] memory _qitokens
) internal {
require(_names.length == _tokens.length, "addQitokenMapping: not same length");
require(_names.length == _qitokens.length, "addQitokenMapping: not same length");
for (uint i = 0; i < _qitokens.length; i++) {
TokenMap memory _data = qiTokenMapping[_names[i]];
require(_data.qitoken == address(0), "addQitokenMapping: mapping added already");
require(_data.token == address(0), "addQitokenMapping: mapping added already");
require(_tokens[i] != address(0), "addQitokenMapping: _tokens address not vaild");
require(_qitokens[i] != address(0), "addQitokenMapping: _qitokens address not vaild");
QiTokenInterface _qitokenContract = QiTokenInterface(_qitokens[i]);
require(_qitokenContract.isQiToken(), "addQitokenMapping: not a qiToken");
if (_tokens[i] != avaxAddr) {
require(_qitokenContract.underlying() == _tokens[i], "addQitokenMapping: mapping mismatch");
}
qiTokenMapping[_names[i]] = TokenMap(
_qitokens[i],
_tokens[i]
);
emit LogQiTokenAdded(_names[i], _tokens[i], _qitokens[i]);
}
}
function updateQitokenMapping(
string[] calldata _names,
address[] memory _tokens,
address[] calldata _qitokens
) external isChief {
require(_names.length == _tokens.length, "updateQitokenMapping: not same length");
require(_names.length == _qitokens.length, "updateQitokenMapping: not same length");
for (uint i = 0; i < _qitokens.length; i++) {
TokenMap memory _data = qiTokenMapping[_names[i]];
require(_data.qitoken != address(0), "updateQitokenMapping: mapping does not exist");
require(_data.token != address(0), "updateQitokenMapping: mapping does not exist");
require(_tokens[i] != address(0), "updateQitokenMapping: _tokens address not vaild");
require(_qitokens[i] != address(0), "updateQitokenMapping: _qitokens address not vaild");
QiTokenInterface _qitokenContract = QiTokenInterface(_qitokens[i]);
require(_qitokenContract.isQiToken(), "updateQitokenMapping: not a qiToken");
if (_tokens[i] != avaxAddr) {
require(_qitokenContract.underlying() == _tokens[i], "addQitokenMapping: mapping mismatch");
}
qiTokenMapping[_names[i]] = TokenMap(
_qitokens[i],
_tokens[i]
);
emit LogQiTokenUpdated(_names[i], _tokens[i], _qitokens[i]);
}
}
function addQitokenMapping(
string[] memory _names,
address[] memory _tokens,
address[] memory _qitokens
) external hasRoleOrIsChief {
_addQitokenMapping(_names, _tokens, _qitokens);
}
function getMapping(string memory _tokenId) external view returns (address, address) {
TokenMap memory _data = qiTokenMapping[_tokenId];
return (_data.token, _data.qitoken);
}
}
contract InstaBenqiMappingAvalanche is Helpers {
string constant public name = "Benqi-Mapping-v1.0";
constructor(
string[] memory _qitokenNames,
address[] memory _tokens,
address[] memory _qitokens
) {
_addQitokenMapping(_qitokenNames, _tokens, _qitokens);
}
}

View File

@ -8,6 +8,6 @@ contract Variables {
/**
* @dev Instapool contract proxy
*/
InstaFlashV2Interface public constant instaPool = InstaFlashV2Interface(0xF77A5935f35aDD4C2f524788805293EF86B87560);
InstaFlashV2Interface public constant instaPool = InstaFlashV2Interface(0x276B88D057b368179480CB707366d497DfC79726);
}

View File

@ -0,0 +1,10 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
contract Events {
event LogFlashBorrow(address token, uint256 tokenAmt);
event LogFlashPayback(address token, uint256 tokenAmt);
event LogFlashMultiBorrow(address[] token, uint256[] tokenAmts);
event LogFlashMultiPayback(address[] token, uint256[] tokenAmts);
}

View File

@ -0,0 +1,11 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
interface InstaFlashV4Interface {
function flashLoan(address[] memory tokens, uint256[] memory amts, uint route, bytes memory data, bytes memory extraData) external;
}
interface AccountInterface {
function enable(address) external;
function disable(address) external;
}

View File

@ -0,0 +1,136 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title Instapool.
* @dev Inbuilt Flash Loan in DSA
*/
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { AccountInterface } from "./interfaces.sol";
import { Stores } from "../../common/stores.sol";
import { Variables } from "./variables.sol";
import { Events } from "./events.sol";
contract LiquidityResolver is Stores, Variables, Events {
using SafeERC20 for IERC20;
/**
* @dev Borrow Flashloan and Cast spells.
* @notice Borrow Flashloan and Cast spells.
* @param token Token Address.
* @param amt Token Amount.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashBorrowAndCast(
address token,
uint amt,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
address[] memory tokens_ = new address[](1);
tokens_[0] = token;
uint[] memory amts_ = new uint[](1);
amts_[0] = amt;
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashBorrow(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Return token to InstaPool.
* @notice Return token to InstaPool.
* @param token Token Address.
* @param amt Token Amount.
* @param getId Get token amount at this ID from `InstaMemory` Contract.
* @param setId Set token amount at this ID in `InstaMemory` Contract.
*/
function flashPayback(
address token,
uint amt,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
IERC20 tokenContract = IERC20(token);
tokenContract.safeTransfer(address(instaPool), _amt);
setUint(setId, _amt);
_eventName = "LogFlashPayback(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Borrow multi-tokens Flashloan and Cast spells.
* @notice Borrow multi-tokens Flashloan and Cast spells.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashMultiBorrowAndCast(
address[] memory tokens_,
uint[] memory amts_,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashMultiBorrow(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
/**
* @dev Return multi-tokens to InstaPool.
* @notice Return multi-tokens to InstaPool.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param getIds Array of getId token amounts.
* @param setIds Array of setId token amounts.
*/
function flashMultiPayback(
address[] memory tokens_,
uint[] memory amts_,
uint[] memory getIds,
uint[] memory setIds
) external payable returns (string memory _eventName, bytes memory _eventParam) {
for (uint i = 0; i < tokens_.length; i++) {
amts_[i] = getUint(getIds[i], amts_[i]);
IERC20(tokens_[i]).safeTransfer(address(instaPool), amts_[i]);
setUint(setIds[i], amts_[i]);
}
_eventName = "LogFlashMultiPayback(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
}
contract ConnectV2InstaPoolV4 is LiquidityResolver {
string public name = "Instapool-v4";
}

View File

@ -0,0 +1,13 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import { InstaFlashV4Interface } from "./interfaces.sol";
contract Variables {
/**
* @dev Instapool contract proxy
*/
InstaFlashV4Interface public constant instaPool = InstaFlashV4Interface(0x619Ad2D02dBeE6ebA3CDbDA3F98430410e892882);
}

View File

@ -0,0 +1,20 @@
pragma solidity ^0.7.0;
contract Events {
event LogDeposit(
address indexed universeVault,
uint256 amountA,
uint256 amountB,
uint256 share0,
uint256 share1
);
event LogWithdraw(
address indexed universeVault,
uint256 amountA,
uint256 amountB,
uint256 share0,
uint256 share1
);
}

View File

@ -0,0 +1,43 @@
pragma solidity ^0.7.6;
import {TokenInterface} from "../../common/interfaces.sol";
import {DSMath} from "../../common/math.sol";
import {Basic} from "../../common/basic.sol";
import "./interface.sol";
abstract contract Helpers is DSMath, Basic {
IUniverseAdapter constant universeAdapter = IUniverseAdapter(0x876861Ad49f911442720cF97c9b3fCe4070F07d5);
function _deposit(
address universeVault,
uint256 amount0,
uint256 amount1
) internal returns(uint256, uint256){
return universeAdapter.depositProxy(universeVault, amount0, amount1);
}
function _withdraw(
address universeVault,
uint256 share0,
uint256 share1
) internal returns(uint256, uint256){
require(share0 > 0 || share1 > 0, "ZERO");
return IVaultV3(universeVault).withdraw(share0, share1);
}
function _approve(address universeVault, uint256 amount0, uint256 amount1) internal {
IVaultV3 universe = IVaultV3(universeVault);
TokenInterface token;
if (amount0 > 0) {
token = universe.token0();
token.approve(address(universeAdapter), amount0);
}
if (amount1 > 0) {
token = universe.token1();
token.approve(address(universeAdapter), amount1);
}
}
}

View File

@ -0,0 +1,24 @@
pragma solidity ^0.7.6;
import "../../common/interfaces.sol";
pragma abicoder v2;
interface IUniverseAdapter {
function depositProxy(
address universeVault,
uint256 amount0,
uint256 amount1
) external returns(uint256, uint256);
}
interface IVaultV3 {
function token0() external returns(TokenInterface);
function token1() external returns(TokenInterface);
function withdraw(uint256 share0, uint256 share1) external returns(uint256, uint256);
}

View File

@ -0,0 +1,92 @@
pragma solidity ^0.7.6;
pragma abicoder v2;
/**
* @title Universe finance
* @dev Maximising uniswap v3 returns
*/
import {TokenInterface} from "../../common/interfaces.sol";
import {Helpers} from "./helpers.sol";
import {Events} from "./events.sol";
abstract contract UniverseFinanceConnect is Helpers, Events {
/**
* @notice Deposit in Universe Vault by Adapter
* @dev Deposit in universe vault
* @param universeVault Universe Official Vault Address
* @param amountA Amount of tokenA
* @param amountB Amount of tokenB
* @param getIds ID to retrieve amountA and amountB
* @param setIds ID to store amountA and amountB
*/
function deposit(
address universeVault,
uint256 amountA,
uint256 amountB,
uint256[] calldata getIds,
uint256[] calldata setIds
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
amountA = getUint(getIds[0], amountA);
amountB = getUint(getIds[1], amountB);
_approve(universeVault, amountA, amountB);
(uint256 share0, uint256 share1) = _deposit(
universeVault,
amountA,
amountB
);
setUint(setIds[0], share0);
setUint(setIds[1], share1);
// EVENT
_eventName = "LogDeposit(address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(
universeVault,
amountA,
amountB,
share0,
share1
);
}
/**
* @notice Withdraw Token0 & Token1 From Universe Vault
* @dev Withdraw supplied token0 and token1 from universe vault
* @param universeVault Universe Official Vault Address
* @param share0 Amount of uToken0.
* @param share1 Amount of uToken1.
* @param getIds ID to retrieve amount of output token
* @param setIds stores the amount of output tokens
*/
function withdraw(
address universeVault,
uint256 share0,
uint256 share1,
uint256[] calldata getIds,
uint256[] calldata setIds
)
external
payable
returns (string memory _eventName, bytes memory _eventParam)
{
share0 = getUint(getIds[0], share0);
share1 = getUint(getIds[1], share1);
(uint256 _amtA, uint256 _amtB) = _withdraw(
universeVault,
share0,
share1
);
setUint(setIds[0], _amtA);
setUint(setIds[1], _amtB);
// EVENT
_eventName = "LogWithdraw(address,uint256,uint256,uint256,uint256)";
_eventParam = abi.encode(universeVault, _amtA, _amtB, share0, share1);
}
}
contract ConnectV2UniverseFinance is UniverseFinanceConnect {
string public constant name = "UniverseFinance-v1";
}

View File

@ -0,0 +1,10 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
contract Events {
event LogFlashBorrow(address token, uint256 tokenAmt);
event LogFlashPayback(address token, uint256 tokenAmt);
event LogFlashMultiBorrow(address[] token, uint256[] tokenAmts);
event LogFlashMultiPayback(address[] token, uint256[] tokenAmts);
}

View File

@ -0,0 +1,11 @@
pragma solidity >=0.7.0;
pragma experimental ABIEncoderV2;
interface InstaFlashV4Interface {
function flashLoan(address[] memory tokens, uint256[] memory amts, uint route, bytes memory data, bytes memory extraData) external;
}
interface AccountInterface {
function enable(address) external;
function disable(address) external;
}

View File

@ -0,0 +1,138 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
/**
* @title Instapool.
* @dev Inbuilt Flash Loan in DSA
*/
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenInterface } from "../../common/interfaces.sol";
import { AccountInterface } from "./interfaces.sol";
import { Stores } from "../../common/stores.sol";
import { Variables } from "./variables.sol";
import { Events } from "./events.sol";
contract LiquidityResolver is Stores, Variables, Events {
using SafeERC20 for IERC20;
/**
* @dev Borrow Flashloan and Cast spells.
* @notice Borrow Flashloan and Cast spells.
* @param token Token Address.
* @param amt Token Amount.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashBorrowAndCast(
address token,
uint amt,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
address[] memory tokens_ = new address[](1);
tokens_[0] = token;
uint[] memory amts_ = new uint[](1);
amts_[0] = amt;
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashBorrow(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Return token to InstaPool.
* @notice Return token to InstaPool.
* @param token Token Address.
* @param amt Token Amount.
* @param getId Get token amount at this ID from `InstaMemory` Contract.
* @param setId Set token amount at this ID in `InstaMemory` Contract.
*/
function flashPayback(
address token,
uint amt,
uint getId,
uint setId
) external payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
IERC20 tokenContract = IERC20(token);
tokenContract.safeTransfer(address(instaPool), _amt);
setUint(setId, _amt);
_eventName = "LogFlashPayback(address,uint256)";
_eventParam = abi.encode(token, amt);
}
/**
* @dev Borrow multi-tokens Flashloan and Cast spells.
* @notice Borrow multi-tokens Flashloan and Cast spells.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param route Flashloan source route.
* @param data targets & data for cast.
* @param extraData to be kept bytes(0) in most cases. Can be useful to decide data for some particular routes
*/
function flashMultiBorrowAndCast(
address[] memory tokens_,
uint[] memory amts_,
uint route,
bytes memory data,
bytes memory extraData
) external payable returns (string memory _eventName, bytes memory _eventParam) {
AccountInterface(address(this)).enable(address(instaPool));
(string[] memory _targets, bytes[] memory callDatas) = abi.decode(data, (string[], bytes[]));
bytes memory callData_ = abi.encodeWithSignature("cast(string[],bytes[],address)", _targets, callDatas, address(instaPool));
instaPool.flashLoan(tokens_, amts_, route, callData_, extraData);
AccountInterface(address(this)).disable(address(instaPool));
_eventName = "LogFlashMultiBorrow(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
/**
* @dev Return multi-tokens to InstaPool.
* @notice Return multi-tokens to InstaPool.
* @param tokens_ Array of Token Addresses.
* @param amts_ Array of Token Amounts.
* @param getIds Array of getId token amounts.
* @param setIds Array of setId token amounts.
*/
function flashMultiPayback(
address[] memory tokens_,
uint[] memory amts_,
uint[] memory getIds,
uint[] memory setIds
) external payable returns (string memory _eventName, bytes memory _eventParam) {
for (uint i = 0; i < tokens_.length; i++) {
amts_[i] = getUint(getIds[i], amts_[i]);
IERC20(tokens_[i]).safeTransfer(address(instaPool), amts_[i]);
setUint(setIds[i], amts_[i]);
}
_eventName = "LogFlashMultiPayback(address[],uint256[])";
_eventParam = abi.encode(tokens_, amts_);
}
}
contract ConnectV2InstaPoolV4 is LiquidityResolver {
string public name = "Instapool-v4";
}

View File

@ -0,0 +1,13 @@
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;
import { InstaFlashV4Interface } from "./interfaces.sol";
contract Variables {
/**
* @dev Instapool contract proxy
*/
InstaFlashV4Interface public constant instaPool = InstaFlashV4Interface(address(0)); // TODO: update address
}

View File

@ -0,0 +1,6 @@
pragma solidity ^0.7.0;
contract Events {
event LogDeposit(uint256 tokenAmt, uint256 getId, uint256 setId);
event LogWithdraw(uint256 tokenAmt, uint256 getId, uint256 setId);
}

View File

@ -0,0 +1,8 @@
pragma solidity ^0.7.0;
import { TokenInterface } from "../../common/interfaces.sol";
abstract contract Helpers {
TokenInterface constant internal wmaticContract = TokenInterface(0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270);
}

View File

@ -0,0 +1,65 @@
pragma solidity ^0.7.0;
/**
* @title WMATIC.
* @dev Wrap and Unwrap WMATIC.
*/
import { DSMath } from "../../common/math.sol";
import { Basic } from "../../common/basic.sol";
import { Events } from "./events.sol";
import { Helpers } from "./helpers.sol";
abstract contract Resolver is Events, DSMath, Basic, Helpers {
/**
* @dev Deposit MATIC into WMATIC.
* @notice Wrap MATIC into WMATIC
* @param amt The amount of MATIC to deposit. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of MATIC deposited.
*/
function deposit(
uint256 amt,
uint256 getId,
uint256 setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
_amt = _amt == uint(-1) ? address(this).balance : _amt;
wmaticContract.deposit{value: _amt}();
setUint(setId, _amt);
_eventName = "LogDeposit(uint256,uint256,uint256)";
_eventParam = abi.encode(_amt, getId, setId);
}
/**
* @dev Withdraw MATIC from WMATIC from Smart Account
* @notice Unwrap MATIC from WMATIC
* @param amt The amount of wmatic to withdraw. (For max: `uint256(-1)`)
* @param getId ID to retrieve amt.
* @param setId ID stores the amount of MATIC withdrawn.
*/
function withdraw(
uint amt,
uint getId,
uint setId
) public payable returns (string memory _eventName, bytes memory _eventParam) {
uint _amt = getUint(getId, amt);
_amt = _amt == uint(-1) ? wmaticContract.balanceOf(address(this)) : _amt;
approve(wmaticContract, wmaticAddr, _amt);
wmaticContract.withdraw(_amt);
setUint(setId, _amt);
_eventName = "LogWithdraw(uint256,uint256,uint256)";
_eventParam = abi.encode(_amt, getId, setId);
}
}
contract ConnectV2WMATICPolygon is Resolver {
string constant public name = "WMATIC-v1.0";
}

View File

@ -1,105 +0,0 @@
require("@nomiclabs/hardhat-waffle");
require("@nomiclabs/hardhat-ethers");
require("@tenderly/hardhat-tenderly");
require("@nomiclabs/hardhat-etherscan");
require("@nomiclabs/hardhat-web3");
require("hardhat-deploy");
require("hardhat-deploy-ethers");
require("dotenv").config();
const { utils } = require("ethers");
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const ALCHEMY_ID = process.env.ALCHEMY_ID;
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY;
if (!process.env.ALCHEMY_ID) {
throw new Error("ENV Variable ALCHEMY_ID not set!");
}
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
compilers: [
{
version: "0.7.6",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
{
version: "0.6.0"
},
{
version: "0.6.2"
},
{
version: "0.6.5"
}
]
},
networks: {
kovan: {
url: `https://eth-kovan.alchemyapi.io/v2/${ALCHEMY_ID}`,
accounts: [`0x${PRIVATE_KEY}`]
},
mainnet: {
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`,
accounts: [`0x${PRIVATE_KEY}`],
timeout: 150000,
gasPrice: parseInt(utils.parseUnits("30", "gwei"))
},
rinkeby: {
url: `https://eth-rinkeby.alchemyapi.io/v2/${ALCHEMY_ID}`,
accounts: [`0x${PRIVATE_KEY}`],
timeout: 150000
},
hardhat: {
forking: {
url: `https://eth-mainnet.alchemyapi.io/v2/${ALCHEMY_ID}`,
blockNumber: 13097100
},
blockGasLimit: 12000000,
gasPrice: parseInt(utils.parseUnits("300", "gwei"))
},
local: {
url: "http://127.0.0.1:8545"
},
matic: {
url: "https://rpc-mainnet.maticvigil.com/",
accounts: [`0x${PRIVATE_KEY}`],
timeout: 150000,
gasPrice: parseInt(utils.parseUnits("1", "gwei"))
},
arbitrum: {
chainId: 42161,
url: `https://arb-mainnet.g.alchemy.com/v2/${ALCHEMY_ID}`,
accounts: [`0x${PRIVATE_KEY}`],
timeout: 150000,
gasPrice: parseInt(utils.parseUnits("2", "gwei"))
},
avax: {
url: "https://api.avax.network/ext/bc/C/rpc",
chainId: 43114,
accounts: [`0x${PRIVATE_KEY}`],
timeout: 150000,
gasPrice: parseInt(utils.parseUnits("225", "gwei"))
}
},
etherscan: {
apiKey: ETHERSCAN_API_KEY
},
tenderly: {
project: process.env.TENDERLY_PROJECT,
username: process.env.TENDERLY_USERNAME
},
mocha: {
timeout: 100 * 1000,
bail: true
}
};

130
hardhat.config.ts Normal file
View File

@ -0,0 +1,130 @@
import "@nomiclabs/hardhat-waffle";
import "@nomiclabs/hardhat-ethers";
import "@tenderly/hardhat-tenderly";
import "@nomiclabs/hardhat-etherscan";
import "@nomiclabs/hardhat-web3";
import "hardhat-deploy";
import "hardhat-deploy-ethers";
import "@typechain/hardhat";
import { resolve } from "path";
import { config as dotenvConfig } from "dotenv";
import { HardhatUserConfig } from "hardhat/config";
import { NetworkUserConfig } from "hardhat/types";
import { utils } from "ethers";
import Web3 from "web3";
dotenvConfig({ path: resolve(__dirname, "./.env") });
const chainIds = {
ganache: 1337,
hardhat: 31337,
mainnet: 1,
avalanche: 43114,
polygon: 137,
arbitrum: 42161,
};
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
if (!alchemyApiKey) {
throw new Error("Please set your ALCHEMY_API_KEY in a .env file");
}
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const ETHERSCAN_API = process.env.ETHERSCAN_API_KEY;
const POLYGONSCAN_API = process.env.POLYGON_API_KEY;
const ARBISCAN_API = process.env.ARBISCAN_API_KEY;
const SNOWTRACE_API = process.env.SNOWTRACE_API_KEY;
const mnemonic =
process.env.MNEMONIC ??
"test test test test test test test test test test test junk";
function createConfig(network: string) {
return {
url: getNetworkUrl(network),
accounts: !!PRIVATE_KEY ? [`0x${PRIVATE_KEY}`] : { mnemonic },
};
}
function getNetworkUrl(networkType: string) {
//console.log(process.env);
if (networkType === "avalanche")
return "https://api.avax.network/ext/bc/C/rpc";
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 return `https://eth-mainnet.alchemyapi.io/v2/${alchemyApiKey}`;
}
function getScanApiKey(networkType: string) {
if (networkType === "avalanche") return SNOWTRACE_API;
else if (networkType === "polygon") return POLYGONSCAN_API;
else if (networkType === "arbitrum") return ARBISCAN_API;
else return ETHERSCAN_API;
}
/**
* @type import('hardhat/config').HardhatUserConfig
*/
const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: "0.7.6",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
{
version: "0.6.0",
},
{
version: "0.6.2",
},
{
version: "0.6.5",
},
],
},
networks: {
hardhat: {
accounts: {
mnemonic,
},
chainId: chainIds.hardhat,
forking: {
url: String(getNetworkUrl(String(process.env.networkType))),
},
},
mainnet: createConfig("mainnet"),
polygon: createConfig("polygon"),
avalanche: createConfig("avalanche"),
arbitrum: createConfig("arbitrum"),
},
paths: {
artifacts: "./artifacts",
cache: "./cache",
sources: "./contracts",
tests: "./test",
},
etherscan: {
apiKey: getScanApiKey(getNetworkUrl(String(process.env.networkType))),
},
typechain: {
outDir: "typechain",
target: "ethers-v5",
},
mocha: {
timeout: 10000 * 1000, // 10,000 seconds
},
// tenderly: {
// project: process.env.TENDERLY_PROJECT,
// username: process.env.TENDERLY_USERNAME,
// },
};
export default config;

24532
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,11 +4,14 @@
"description": "",
"directories": {},
"scripts": {
"test": "hardhat test",
"test": "hardhat run scripts/tests/global-test.ts",
"coverage": "./node_modules/.bin/solidity-coverage",
"check": "node status-checks/huskyCheck.js",
"check-husky": "node status-checks/huskyCheck.js",
"deploy": "node scripts/deployConnectorsFromCmd.js"
"deploy": "node scripts/deployConnectorsFromCmd.js",
"test:runner": "hardhat run scripts/tests/run-tests.ts",
"typechain": "hardhat typechain",
"compile": "hardhat compile"
},
"repository": {
"type": "git",
@ -22,33 +25,45 @@
"homepage": "https://github.com/InstaDApp/dsa-connectors-new#readme",
"dependencies": {
"@openzeppelin/contracts": "^3.4.0-solc-0.7",
"@typechain/ethers-v5": "^8.0.5",
"@typechain/hardhat": "^3.0.0",
"@uniswap/v3-core": "^1.0.0",
"@uniswap/v3-periphery": "^1.2.1",
"chalk": "^4.1.2",
"@uniswap/v3-periphery": "^1.3.0",
"chalk": "^5.0.0",
"dotenv": "^10.0.0",
"hardhat-docgen": "^1.1.2",
"hardhat-docgen": "^1.2.0",
"inquirer": "^8.2.0",
"minimist": "^1.2.5",
"solc": "^0.7.6"
"solc": "^0.8.10",
"typechain": "^6.0.5"
},
"devDependencies": {
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^2.1.7",
"@nomiclabs/hardhat-ethers": "^2.0.3",
"@nomiclabs/hardhat-etherscan": "^2.1.8",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/test-helpers": "^0.5.15",
"@studydefi/money-legos": "^2.4.2",
"@tenderly/hardhat-tenderly": "^1.0.12",
"@tenderly/hardhat-tenderly": "^1.0.13",
"@types/chai": "^4.2.22",
"@types/chai-as-promised": "^7.1.4",
"@types/inquirer": "^8.1.3",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.11",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"ethereum-waffle": "^3.4.0",
"ethers": "^5.5.1",
"hardhat": "^2.6.7",
"hardhat-deploy": "^0.9.4",
"ethers": "^5.5.2",
"hardhat": "^2.7.0",
"hardhat-deploy": "^0.9.14",
"hardhat-deploy-ethers": "^0.3.0-beta.11",
"prettier": "^2.4.1",
"prettier-plugin-solidity": "^1.0.0-beta.18",
"solhint": "^3.3.6",
"husky": "^7.0.4",
"solidity-coverage": "0.5.11",
"web3": "^1.6.0"
"solidity-coverage": "0.7.17",
"ts-node": "^10.4.0",
"typescript": "^4.5.2",
"web3": "^1.6.1"
}
}

View File

@ -1,15 +0,0 @@
const hre = require("hardhat");
const { ethers } = hre;
const addresses = require("./constant/addresses");
const abis = require("./constant/abis");
const instaImplementations_m1 = require("../deployements/mainnet/Implementation_m1.sol/InstaImplementationM1.json")
module.exports = async function (owner) {
const instaIndex = await ethers.getContractAt(abis.core.instaIndex, addresses.core.instaIndex)
const tx = await instaIndex.build(owner, 2, owner);
const receipt = await tx.wait()
const event = receipt.events.find(a => a.event === "LogAccountCreated")
return await ethers.getContractAt(instaImplementations_m1.abi, event.args.account)
};

View File

@ -1,4 +1,4 @@
module.exports = {
export const abis: Record<string, any> = {
core: {
connectorsV2: require("./abi/core/connectorsV2.json"),
instaIndex: require("./abi/core/instaIndex.json"),

View File

@ -1,11 +0,0 @@
module.exports = {
connectors: {
basic: "0xe5398f279175962E56fE4c5E0b62dc7208EF36c6",
auth: "0xd1aff9f2acf800c876c409100d6f39aea93fc3d9",
"INSTAPOOL-A": "0x5806af7ab22e2916fa582ff05731bf7c682387b2",
},
core: {
connectorsV2: "0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11",
instaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723",
},
};

View File

@ -1,6 +0,0 @@
module.exports = {
address_zero: "0x0000000000000000000000000000000000000000",
eth_addr: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
max_value: "115792089237316195423570985008687907853269984665640564039457584007913129639935"
};

View File

@ -0,0 +1,6 @@
export const constants = {
address_zero: "0x0000000000000000000000000000000000000000",
native_address: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
max_value:
"115792089237316195423570985008687907853269984665640564039457584007913129639935",
};

View File

@ -1,23 +0,0 @@
module.exports = {
"eth": {
"type": "token",
"symbol": "ETH",
"name": "Ethereum",
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"decimals": 18
},
"dai": {
"type": "token",
"symbol": "DAI",
"name": "DAI Stable",
"address": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"decimals": 18
},
"usdc": {
"type": "token",
"symbol": "USDC",
"name": "USD Coin",
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"decimals": 6
}
}

View File

@ -1,38 +0,0 @@
const hre = require("hardhat");
const { ethers } = hre;
const deployConnector = require("./deployConnector");
async function main() {
const accounts = await hre.ethers.getSigners()
const wallet = accounts[0]
const connectMapping = {
'1INCH-A': 'ConnectV2OneInch',
'1INCH-B': 'ConnectV2OneProto',
'AAVE-V1-A': 'ConnectV2AaveV1',
'AAVE-V2-A': 'ConnectV2AaveV2',
'AUTHORITY-A': 'ConnectV2Auth',
'BASIC-A': 'ConnectV2Basic',
'COMP-A': 'ConnectV2COMP',
'COMPOUND-A': 'ConnectV2Compound',
'DYDX-A': 'ConnectV2Dydx',
'FEE-A': 'ConnectV2Fee',
'GELATO-A': 'ConnectV2Gelato',
'MAKERDAO-A': 'ConnectV2Maker',
'UNISWAP-A': 'ConnectV2UniswapV2'
}
const addressMapping = {}
for (const key in connectMapping) {
addressMapping[key] = await deployConnector(connectMapping[key])
}
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});

View File

@ -1,19 +0,0 @@
const abis = require("./constant/abis");
const addresses = require("./constant/addresses");
const hre = require("hardhat");
const { ethers, waffle } = hre;
const { deployContract } = waffle;
const fs = require("fs")
module.exports = async function ({connectorName, contractArtifact, signer, connectors}) {
const connectorInstanace = await deployContract(signer, contractArtifact, []);
await connectors.connect(signer).addConnectors([connectorName], [connectorInstanace.address])
addresses.connectors[connectorName] = connectorInstanace.address
abis.connectors[connectorName] = contractArtifact.abi;
return connectorInstanace;
};

View File

@ -1,27 +0,0 @@
const hre = require("hardhat");
const { ethers, deployments, getUnnamedAccounts } = hre;
const { deploy } = deployments;
async function main() {
const deployer = (await getUnnamedAccounts())[0]
const connector = "ConnectV2InstaPoolV3Avalanche"
const connectorInstance = await deploy(connector, {
from: deployer,
})
console.log(`${connector} deployed: `, connectorInstance.address);
await hre.run("sourcify", {
address: connectorInstance.address,
})
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});

View File

@ -1,24 +0,0 @@
const hre = require("hardhat");
const { ethers } = hre;
module.exports = async (connectorName) => {
const Connector = await ethers.getContractFactory(connectorName);
const connector = await Connector.deploy();
await connector.deployed();
console.log(`${connectorName} Deployed: ${connector.address}`);
try {
await hre.run("verify:verify", {
address: connector.address,
constructorArguments: []
}
)
} catch (error) {
console.log(`Failed to verify: ${connectorName}@${connector.address}`)
console.log(error)
console.log()
}
return connector.address
}

View File

@ -1,105 +0,0 @@
const fs = require("fs");
const hre = require("hardhat");
const { ethers } = hre;
let args = process.argv;
args = args.splice(2, args.length);
let params = {};
for (let i = 0; i < args.length; i += 2) {
if (args[i][0] !== "-" || args[i][1] !== "-") {
console.log("Please add '--' for the key");
process.exit(-1);
}
let key = args[i].slice(2, args[i].length);
params[key] = args[i + 1];
}
if (!params.hasOwnProperty('connector')) {
console.error("Should include connector params")
process.exit(-1);
}
if (!params.hasOwnProperty('network')) {
console.error("Should include network params")
process.exit(-1);
}
if (!params.hasOwnProperty('gasPrice')) {
console.error("Should include gas params")
process.exit(-1);
}
let privateKey = process.env.PRIVATE_KEY;
let provider = new ethers.providers.JsonRpcProvider(hre.config.networks[params['network']].url);
let wallet = new ethers.Wallet(privateKey, provider);
hre.network.name = params['networkName'];
hre.network.config = hre.config.networks[params['networkName']];
hre.network.provider = provider;
let contracts = [];
const parseFile = async (filePath) => {
const data = fs.readFileSync(filePath, "utf-8");
let parsedData = data.split("contract ");
parsedData = parsedData[parsedData.length - 1].split(" ");
parsedData = parsedData[0];
return parsedData;
}
const parseDir = async (root, basePath, addPath) => {
for(let i = 0; i < root.length; i++) {
addPath = "/" + root[i];
const dir = fs.readdirSync(basePath + addPath);
if(dir.indexOf("main.sol") !== -1) {
const fileData = await parseFile(basePath + addPath + "/main.sol");
contracts.push(fileData)
} else {
await parseDir(dir, basePath + addPath, "");
}
}
}
const main = async () => {
const mainnet = fs.readdirSync("./contracts/mainnet/connectors/");
const polygon = fs.readdirSync("./contracts/polygon/connectors/");
let basePathMainnet = "./contracts/mainnet/connectors/";
let basePathPolygon = "./contracts/polygon/connectors/";
const connectorName = params['connector'];
await parseDir(mainnet, basePathMainnet, "");
await parseDir(polygon, basePathPolygon, "");
if(contracts.indexOf(connectorName) === -1) {
throw new Error("can not find the connector!\n" + "supported connector names are:\n" + contracts.join("\n"));
}
const Connector = await ethers.getContractFactory(connectorName);
const connector = await Connector.connect(wallet).deploy({ gasPrice: ethers.utils.parseUnits(params['gasPrice'], "gwei") });
await connector.deployed();
console.log(`${connectorName} Deployed: ${connector.address}`);
try {
await hre.run("verify:verify", {
address: connector.address,
constructorArguments: []
}
)
} catch (error) {
console.log(`Failed to verify: ${connectorName}@${connector.address}`)
console.log(error)
}
return connector.address
}
main()
.then(() => {
console.log("Done successfully");
process.exit(0)
})
.catch(err => {
console.log("error:", err);
process.exit(1);
})

View File

@ -1,36 +0,0 @@
const hre = require('hardhat')
const { ethers } = hre
async function main () {
if (hre.network.name === 'mainnet') {
console.log(
'\n\n Deploying Contracts to mainnet. Hit ctrl + c to abort'
)
} else if (hre.network.name === 'hardhat') {
console.log(
'\n\n Deploying Contracts to hardhat.'
)
}
const InstaMappingController = await ethers.getContractFactory('InstaMappingController')
const instaMappingController = await InstaMappingController.deploy()
await instaMappingController.deployed()
console.log('InstaMappingController deployed: ', instaMappingController.address)
if (hre.network.name === 'mainnet') {
await hre.run('verify:verify', {
address: instaMappingController.address,
constructorArguments: []
})
} else if (hre.network.name === 'hardhat') {
console.log("Contracts deployed.")
}
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error)
process.exit(1)
})

View File

@ -1,38 +0,0 @@
const hre = require('hardhat')
const { ethers } = hre
async function main () {
if (hre.network.name === 'mainnet') {
console.log(
'\n\n Deploying Contracts to mainnet. Hit ctrl + c to abort'
)
} else if (hre.network.name === 'hardhat') {
console.log(
'\n\n Deploying Contracts to hardhat.'
)
}
const mappingContract = "CONTRACT_NAME"
const InstaProtocolMapping = await ethers.getContractFactory(mappingContract)
const instaProtocolMapping = await InstaProtocolMapping.deploy()
await instaProtocolMapping.deployed()
console.log(`${mappingContract} deployed: `, instaProtocolMapping.address)
if (hre.network.name === 'mainnet') {
await hre.run('verify:verify', {
address: instaProtocolMapping.address,
constructorArguments: []
})
} else if (hre.network.name === 'hardhat') {
console.log("Contracts deployed.")
}
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error)
process.exit(1)
})

View File

@ -1,23 +0,0 @@
const hre = require("hardhat");
const { ethers } = hre;
const deployConnector = require("./deployConnector");
async function main() {
const address = await deployConnector("ConnectOne") // Example
const connectorsAbi = [
"function addConnectors(string[] _connectorNames, address[] _connectors)"
]
const connectorsContract = new ethers.Contract("0x84b457c6D31025d56449D5A01F0c34bF78636f67", connectorsAbi, ethers.provider);
await connectorsContract.addConnectors(['1inch'], [address])
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,35 @@
import { ethers } from "hardhat";
import { deployConnector } from "./deployConnector";
async function main() {
const accounts = await ethers.getSigners();
const connectMapping: Record<string, string> = {
"1INCH-A": "ConnectV2OneInch",
"1INCH-B": "ConnectV2OneProto",
"AAVE-V1-A": "ConnectV2AaveV1",
"AAVE-V2-A": "ConnectV2AaveV2",
"AUTHORITY-A": "ConnectV2Auth",
"BASIC-A": "ConnectV2Basic",
"COMP-A": "ConnectV2COMP",
"COMPOUND-A": "ConnectV2Compound",
"DYDX-A": "ConnectV2Dydx",
"FEE-A": "ConnectV2Fee",
"GELATO-A": "ConnectV2Gelato",
"MAKERDAO-A": "ConnectV2Maker",
"UNISWAP-A": "ConnectV2UniswapV2",
};
const addressMapping: Record<string, string> = {};
for (const key in connectMapping) {
addressMapping[key] = await deployConnector(connectMapping[key]);
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,24 @@
import hre from "hardhat";
const { ethers, deployments, getUnnamedAccounts } = hre;
const { deploy } = deployments;
async function main() {
const deployer = (await getUnnamedAccounts())[0];
const connector = "// Add connector name over here Eg: ConnectV2InstaPoolV3Avalanche"
const connectorInstance = await deploy(connector, {
from: deployer,
});
console.log(`${connector} deployed: `, connectorInstance.address);
await hre.run("sourcify", {
address: connectorInstance.address,
});
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -1,8 +1,6 @@
const hre = require("hardhat");
import hre from "hardhat";
const { ethers } = hre;
async function main() {
const CONNECTORS_V2 = "0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11";
const ctokenMapping = {
@ -23,7 +21,7 @@ async function main() {
"AAVE-A": "0xe65cdb6479bac1e22340e4e755fae7e509ecd06c",
"TUSD-A": "0x12392f67bdf24fae0af363c24ac620a2f67dad86",
"LINK-A": "0xface851a4921ce59e912d19329929ce6da6eb0c7",
}
};
const tokenMapping = {
"ETH-A": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
@ -43,7 +41,7 @@ async function main() {
"AAVE-A": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9",
"TUSD-A": "0x0000000000085d4780B73119b644AE5ecd22b376",
"LINK-A": "0x514910771af9ca656af840dff83e8264ecf986ca",
}
};
const Mapping = await ethers.getContractFactory("InstaCompoundMapping");
const mapping = await Mapping.deploy(
@ -58,25 +56,24 @@ async function main() {
try {
await hre.run("verify:verify", {
address: mapping.address,
constructorArguments: [
CONNECTORS_V2,
Object.keys(ctokenMapping),
Object.values(tokenMapping),
Object.values(ctokenMapping)
]
}
)
} catch (error) {
console.log(`Failed to verify: InstaCompoundMapping@${mapping.address}`)
console.log(error)
console.log()
}
address: mapping.address,
constructorArguments: [
CONNECTORS_V2,
Object.keys(ctokenMapping),
Object.values(tokenMapping),
Object.values(ctokenMapping),
],
});
} catch (error) {
console.log(`Failed to verify: InstaCompoundMapping@${mapping.address}`);
console.log(error);
console.log();
}
}
main()
.then(() => process.exit(0))
.catch(error => {
.catch((error) => {
console.error(error);
process.exit(1);
});
});

View File

@ -0,0 +1,10 @@
import { ethers } from "hardhat";
export const deployConnector = async (connectorName: string) => {
const Connector = await ethers.getContractFactory(connectorName);
const connector = await Connector.deploy();
await connector.deployed();
console.log(`${connectorName} Deployed: ${connector.address}`);
return connector.address;
};

View File

@ -0,0 +1,111 @@
import fs from "fs";
import hre from "hardhat"
const { ethers, network, config } = hre;
let args = process.argv;
args = args.splice(2, args.length);
let params: Record<string, string> = {};
for (let i = 0; i < args.length; i += 2) {
if (args[i][0] !== "-" || args[i][1] !== "-") {
console.log("Please add '--' for the key");
process.exit(-1);
}
let key = args[i].slice(2, args[i].length);
params[key] = args[i + 1];
}
if (!params.hasOwnProperty("connector")) {
console.error("Should include connector params");
process.exit(-1);
}
if (!params.hasOwnProperty("network")) {
console.error("Should include network params");
process.exit(-1);
}
if (!params.hasOwnProperty("gasPrice")) {
console.error("Should include gas params");
process.exit(-1);
}
let privateKey = String(process.env.PRIVATE_KEY);
let provider = new ethers.providers.JsonRpcProvider(
config.networks[params["network"]].url
);
let wallet = new ethers.Wallet(privateKey, provider);
network.name = params["networkName"];
network.config = config.networks[params["networkName"]];
network.provider = provider;
let contracts: (string | string[])[] = [];
const parseFile = async (filePath: fs.PathOrFileDescriptor) => {
const data = fs.readFileSync(filePath, "utf-8");
let parsedData = data.split("contract ");
parsedData = parsedData[parsedData.length - 1].split(" ");
return parsedData[0];
};
const parseDir = async (root: string | any[], basePath: string, addPath: string) => {
for (let i = 0; i < root.length; i++) {
addPath = "/" + root[i];
const dir = fs.readdirSync(basePath + addPath);
if (dir.indexOf("main.sol") !== -1) {
const fileData = await parseFile(basePath + addPath + "/main.sol");
contracts.push(fileData);
} else {
await parseDir(dir, basePath + addPath, "");
}
}
};
const main = async () => {
const mainnet = fs.readdirSync("./contracts/mainnet/connectors/");
const polygon = fs.readdirSync("./contracts/polygon/connectors/");
let basePathMainnet = "./contracts/mainnet/connectors/";
let basePathPolygon = "./contracts/polygon/connectors/";
const connectorName = params["connector"];
await parseDir(mainnet, basePathMainnet, "");
await parseDir(polygon, basePathPolygon, "");
if (contracts.indexOf(connectorName) === -1) {
throw new Error(
"can not find the connector!\n" +
"supported connector names are:\n" +
contracts.join("\n")
);
}
const Connector = await ethers.getContractFactory(connectorName);
const connector = await Connector.connect(wallet).deploy({
gasPrice: ethers.utils.parseUnits(params["gasPrice"], "gwei"),
});
await connector.deployed();
console.log(`${connectorName} Deployed: ${connector.address}`);
try {
await hre.run("verify:verify", {
address: connector.address,
constructorArguments: [],
});
} catch (error) {
console.log(`Failed to verify: ${connectorName}@${connector.address}`);
console.log(error);
}
return connector.address;
};
main()
.then(() => {
console.log("Done successfully");
process.exit(0);
})
.catch((err) => {
console.log("error:", err);
process.exit(1);
});

View File

@ -0,0 +1,37 @@
import hre from "hardhat";
const { ethers } = hre;
async function main() {
if (hre.network.name === "mainnet") {
console.log("\n\n Deploying Contracts to mainnet. Hit ctrl + c to abort");
} else if (hre.network.name === "hardhat") {
console.log("\n\n Deploying Contracts to hardhat.");
}
const InstaMappingController = await ethers.getContractFactory(
"InstaMappingController"
);
const instaMappingController = await InstaMappingController.deploy();
await instaMappingController.deployed();
console.log(
"InstaMappingController deployed: ",
instaMappingController.address
);
if (hre.network.name === "mainnet") {
await hre.run("verify:verify", {
address: instaMappingController.address,
constructorArguments: [],
});
} else if (hre.network.name === "hardhat") {
console.log("Contracts deployed.");
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,34 @@
import hre from "hardhat";
const { ethers } = hre;
async function main() {
if (hre.network.name === "mainnet") {
console.log("\n\n Deploying Contracts to mainnet. Hit ctrl + c to abort");
} else if (hre.network.name === "hardhat") {
console.log("\n\n Deploying Contracts to hardhat.");
}
const mappingContract = "CONTRACT_NAME";
const InstaProtocolMapping = await ethers.getContractFactory(mappingContract);
const instaProtocolMapping = await InstaProtocolMapping.deploy();
await instaProtocolMapping.deployed();
console.log(`${mappingContract} deployed: `, instaProtocolMapping.address);
if (hre.network.name === "mainnet") {
await hre.run("verify:verify", {
address: instaProtocolMapping.address,
constructorArguments: [],
});
} else if (hre.network.name === "hardhat") {
console.log("Contracts deployed.");
}
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -1,16 +0,0 @@
const abis = require("./constant/abis");
const addresses = require("./constant/addresses");
const { web3 } = hre;
const encodeSpells = require("./encodeSpells.js")
module.exports = function (spells) {
const encodeSpellsData = encodeSpells(spells);
const targetType = "string[]";
let argTypes = [targetType, "bytes[]"];
return web3.eth.abi.encodeParameters(argTypes, [
encodeSpellsData[0],
encodeSpellsData[1],
]);
};

View File

@ -1,18 +0,0 @@
const abis = require("./constant/abis");
const addresses = require("./constant/addresses");
const { web3 } = hre;
module.exports = function (spells) {
const targets = spells.map(a => a.connector)
const calldatas = spells.map(a => {
const functionName = a.method;
// console.log(functionName)
const abi = abis.connectors[a.connector].find(b => {
return b.name === functionName
});
// console.log(functionName)
if (!abi) throw new Error("Couldn't find function")
return web3.eth.abi.encodeFunctionCall(abi, a.args)
})
return [targets, calldatas]
};

View File

@ -1,25 +0,0 @@
const hre = require("hardhat");
const { ethers } = hre;
const addresses = require("./constant/addresses");
const abis = require("./constant/abis");
module.exports = async function() {
const [_, __, ___, wallet3] = await ethers.getSigners();
const instaIndex = new ethers.Contract(
addresses.core.instaIndex,
abis.core.instaIndex,
wallet3
);
const masterAddress = await instaIndex.master(); // TODO: make it constant?
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [masterAddress],
});
await wallet3.sendTransaction({
to: masterAddress,
value: ethers.utils.parseEther("10"),
});
return await ethers.getSigner(masterAddress);
};

View File

@ -1,15 +0,0 @@
const hre = require("hardhat");
const { ethers } = hre;
const addresses = require("./constant/addresses");
const abis = require("../constant/abis");
const instaImplementations_m1 = require("../../deployements/mainnet/Implementation_m1.sol/InstaImplementationM1.json")
module.exports = async function (owner) {
const instaIndex = await ethers.getContractAt(abis.core.instaIndex, addresses.core.instaIndex)
const tx = await instaIndex.build(owner, 2, owner);
const receipt = await tx.wait()
const event = receipt.events.find(a => a.event === "LogAccountCreated")
return await ethers.getContractAt(instaImplementations_m1.abi, event.args.account)
};

View File

@ -1,7 +0,0 @@
module.exports = {
address_zero: "0x0000000000000000000000000000000000000000",
eth_addr: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
matic_addr: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
max_value: "115792089237316195423570985008687907853269984665640564039457584007913129639935"
};

View File

@ -1,30 +0,0 @@
module.exports = {
"matic": {
"type": "token",
"symbol": "MATIC",
"name": "Matic",
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"decimals": 18
},
"eth": {
"type": "token",
"symbol": "ETH",
"name": "Ethereum",
"address": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"decimals": 18
},
"dai": {
"type": "token",
"symbol": "DAI",
"name": "DAI Stable",
"address": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
"decimals": 18
},
"usdc": {
"type": "token",
"symbol": "USDC",
"name": "USD Coin",
"address": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
"decimals": 6
}
}

View File

@ -1,19 +0,0 @@
const abis = require("../constant/abis");
const addresses = require("./constant/addresses");
const hre = require("hardhat");
const { ethers, waffle } = hre;
const { deployContract } = waffle;
const fs = require("fs")
module.exports = async function ({connectorName, contractArtifact, signer, connectors}) {
const connectorInstanace = await deployContract(signer, contractArtifact, []);
await connectors.connect(signer).addConnectors([connectorName], [connectorInstanace.address])
addresses.connectors[connectorName] = connectorInstanace.address
abis.connectors[connectorName] = contractArtifact.abi;
return connectorInstanace;
};

View File

@ -1,18 +0,0 @@
const abis = require("../constant/abis");
const addresses = require("./constant/addresses");
const { web3 } = hre;
module.exports = function (spells) {
const targets = spells.map(a => a.connector)
const calldatas = spells.map(a => {
const functionName = a.method;
// console.log(functionName)
const abi = abis.connectors[a.connector].find(b => {
return b.name === functionName
});
// console.log(functionName)
if (!abi) throw new Error("Couldn't find function")
return web3.eth.abi.encodeFunctionCall(abi, a.args)
})
return [targets, calldatas]
};

View File

@ -1,25 +0,0 @@
const hre = require("hardhat");
const { ethers } = hre;
const addresses = require("./constant/addresses");
const abis = require("../constant/abis");
module.exports = async function() {
const [_, __, ___, wallet3] = await ethers.getSigners();
const instaIndex = new ethers.Contract(
addresses.core.instaIndex,
abis.core.instaIndex,
wallet3
);
const masterAddress = await instaIndex.master(); // TODO: make it constant?
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [masterAddress],
});
await wallet3.sendTransaction({
to: masterAddress,
value: ethers.utils.parseEther("10"),
});
return await ethers.getSigner(masterAddress);
};

View File

@ -1,18 +1,20 @@
const { ethers } = require("hardhat");
const impersonateAccounts = require("./impersonate");
import { Provider } from "@ethersproject/abstract-provider";
import { Signer } from "@ethersproject/abstract-signer";
import { ethers } from "hardhat";
import { impersonateAccounts } from "./impersonate";
const mineTx = async (tx) => {
const mineTx = async (tx: any) => {
await (await tx).wait();
};
const tokenMapping = {
const tokenMapping: Record<string, any> = {
usdc: {
impersonateSigner: "0xfcb19e6a322b27c06842a71e8c725399f049ae3a",
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
abi: [
"function mint(address _to, uint256 _amount) external returns (bool);",
],
process: async function(owner, to, amt) {
process: async function(owner: Signer | Provider, to: any, amt: any) {
const contract = new ethers.Contract(this.address, this.abi, owner);
await mineTx(contract.mint(to, amt));
@ -22,7 +24,7 @@ const tokenMapping = {
impersonateSigner: "0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503",
abi: ["function transfer(address to, uint value)"],
address: "0x6b175474e89094c44da98b954eedeac495271d0f",
process: async function(owner, to, amt) {
process: async function(owner: Signer | Provider, to: any, amt: any) {
const contract = new ethers.Contract(this.address, this.abi, owner);
await mineTx(contract.transfer(to, amt));
},
@ -34,7 +36,7 @@ const tokenMapping = {
"function issue(uint amount)",
"function transfer(address to, uint value)",
],
process: async function(owner, address, amt) {
process: async function(owner: Signer | Provider, address: any, amt: any) {
const contract = new ethers.Contract(this.address, this.abi, owner);
await mineTx(contract.issue(amt));
@ -45,7 +47,7 @@ const tokenMapping = {
impersonateSigner: "0xCA06411bd7a7296d7dbdd0050DFc846E95fEBEB7",
address: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
abi: ["function mint(address _to, uint256 _amount) public returns (bool)"],
process: async function(owner, address, amt) {
process: async function(owner: Signer | Provider, address: any, amt: any) {
const contract = new ethers.Contract(this.address, this.abi, owner);
await mineTx(contract.mint(address, amt));
},
@ -54,20 +56,19 @@ const tokenMapping = {
impersonateSigner: "0x75e89d5979E4f6Fba9F97c104c2F0AFB3F1dcB88",
address: "0x6f40d4a6237c257fff2db00fa0510deeecd303eb",
abi: ["function transfer(address to, uint value)"],
process: async function(owner, address, amt) {
process: async function(owner: Signer | Provider, address: any, amt: any) {
const contract = new ethers.Contract(this.address, this.abi, owner);
await mineTx(contract.transfer(address, amt));
},
}
},
};
module.exports = async (tokenName, address, amt) => {
export async function addLiquidity(tokenName: string, address: any, amt: any) {
const [signer] = await ethers.getSigners();
tokenName = tokenName.toLowerCase();
if (!tokenMapping[tokenName]) {
throw new Error(
"Add liquidity doesn't support the following token: ",
tokenName
`Add liquidity doesn't support the following token: ${tokenName}`
);
}
@ -84,4 +85,4 @@ module.exports = async (tokenName, address, amt) => {
});
await token.process(impersonatedSigner, address, amt);
};
}

View File

@ -0,0 +1,11 @@
export const addresses: Record<string, any> = {
connectors: {
// basic: "0x6214f9c4F9700fc7a50B5f9aEEB819d647406Ac7",
// auth: "0xD6daA927ad756a4022858dddcc4E26137b30DB4D",
// "INSTAPOOL-A": "0x8f1e38c53af7bD2b2bE01b9580911b7Cca504F1b",
},
core: {
connectorsV2: "0x67fCE99Dd6d8d659eea2a1ac1b8881c57eb6592B",
instaIndex: "0x1eE00C305C51Ff3bE60162456A9B533C07cD9288",
},
};

View File

@ -0,0 +1,23 @@
export const tokens = {
eth: {
type: "token",
symbol: "ETH",
name: "Ethereum",
address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
decimals: 18,
},
dai: {
type: "token",
symbol: "DAI",
name: "DAI Stable",
address: "0xd586e7f844cea2f87f50152665bcbc2c279d8d70",
decimals: 18,
},
usdc: {
type: "token",
symbol: "USDC",
name: "USD Coin",
address: "0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664",
decimals: 6,
},
};

View File

@ -0,0 +1,11 @@
export const addresses: Record<string, any> = {
connectors: {
// basic: "0x6214f9c4F9700fc7a50B5f9aEEB819d647406Ac7",
// auth: "0xD6daA927ad756a4022858dddcc4E26137b30DB4D",
// "INSTAPOOL-A": "0x8f1e38c53af7bD2b2bE01b9580911b7Cca504F1b",
},
core: {
connectorsV2: "0x67fCE99Dd6d8d659eea2a1ac1b8881c57eb6592B",
instaIndex: "0x1eE00C305C51Ff3bE60162456A9B533C07cD9288",
},
};

View File

@ -0,0 +1,23 @@
export const tokens = {
eth: {
type: "token",
symbol: "ETH",
name: "Ethereum",
address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
decimals: 18,
},
dai: {
type: "token",
symbol: "DAI",
name: "DAI Stable",
address: "0xd586e7f844cea2f87f50152665bcbc2c279d8d70",
decimals: 18,
},
usdc: {
type: "token",
symbol: "USDC",
name: "USD Coin",
address: "0xa7d7079b0fead91f3e65f86e8915cb59c1a4c664",
decimals: 6,
},
};

View File

@ -0,0 +1,29 @@
import { ethers } from "hardhat";
import { addresses as addressesPolygon } from "./polygon/addresses";
import { addresses as addressesArbitrum } from "./arbitrum/addresses";
import { addresses as addressesAvalanche } from "./avalanche/addresses";
import { addresses } from "./mainnet/addresses";
import { abis } from "../constant/abis";
import { abi } from "../../deployements/mainnet/Implementation_m1.sol/InstaImplementationM1.json";
function getAddress(network: string | undefined) {
if (network === "polygon") return addressesPolygon.core.instaIndex;
else if (network === "arbitrum") return addressesArbitrum.core.instaIndex;
else if (network === "avalanche") return addressesAvalanche.core.instaIndex;
else return addresses.core.instaIndex;
}
export async function buildDSAv2(owner: any) {
const instaIndex = await ethers.getContractAt(
abis.core.instaIndex,
getAddress(String(process.env.networkType))
);
const tx = await instaIndex.build(owner, 2, owner);
const receipt = await tx.wait();
const event = receipt.events.find(
(a: { event: string }) => a.event === "LogAccountCreated"
);
return await ethers.getContractAt(abi, event.args.account);
}

33
scripts/tests/command.ts Normal file
View File

@ -0,0 +1,33 @@
import { execFile, spawn } from "child_process";
interface ICommand {
readonly cmd: string;
readonly args: string[];
readonly env: {
[param: string]: string;
};
}
export async function execScript(input: ICommand): Promise<number> {
return new Promise((resolve, reject) => {
let cmdEnv = Object.create(process.env);
for (let param in input.env) {
cmdEnv[param] = input.env[param];
}
const proc = spawn(input.cmd, [...input.args], {
env: cmdEnv,
shell: true,
stdio: "inherit",
});
proc.on("exit", (code) => {
if (code !== 0) {
reject(code);
return;
}
resolve(code);
});
});
}

View File

@ -0,0 +1,47 @@
import { addresses as addressesPolygon } from "./polygon/addresses";
import { addresses } from "./mainnet/addresses";
import { abis } from "../constant/abis";
import { addresses as addressesArbitrum } from "./arbitrum/addresses";
import { addresses as addressesAvalanche } from "./avalanche/addresses";
import hre from "hardhat";
import type { Signer, Contract } from "ethers";
import type { ContractJSON } from "ethereum-waffle/dist/esm/ContractJSON";
const { ethers, waffle } = hre;
const { deployContract } = waffle;
interface DeployInterface {
connectorName: string;
contractArtifact: ContractJSON;
signer: Signer;
connectors: Contract;
}
function getAddress(network: string | undefined) {
if (network === "polygon") return addressesPolygon;
else if (network === "arbitrum") return addressesArbitrum;
else if (network === "avalanche") return addressesAvalanche;
else return addresses;
}
export async function deployAndEnableConnector(
{
connectorName,
contractArtifact,
signer,
connectors
} : DeployInterface
) {
const connectorInstanace: Contract = await deployContract(signer, contractArtifact);
await connectors
.connect(signer)
.addConnectors([connectorName], [connectorInstanace.address]);
getAddress(String(process.env.networkType)).connectors[connectorName] =
connectorInstanace.address;
abis.connectors[connectorName] = contractArtifact.abi;
return connectorInstanace;
}

View File

@ -0,0 +1,13 @@
import hre from "hardhat";
const { web3 } = hre;
import { encodeSpells } from "./encodeSpells";
export default function encodeFlashcastData(spells: any) {
const encodeSpellsData = encodeSpells(spells);
let argTypes = ["string[]", "bytes[]"];
return web3.eth.abi.encodeParameters(argTypes, [
encodeSpellsData[0],
encodeSpellsData[1],
]);
};

View File

@ -0,0 +1,17 @@
import { web3 } from "hardhat";
import { abis } from "../constant/abis";
export function encodeSpells(spells: any[]) {
const targets = spells.map((a) => a.connector);
const calldatas = spells.map((a) => {
const functionName = a.method;
// console.log(functionName)
const abi = abis.connectors[a.connector].find((b: { name: any }) => {
return b.name === functionName;
});
// console.log(functionName)
if (!abi) throw new Error("Couldn't find function");
return web3.eth.abi.encodeFunctionCall(abi, a.args);
});
return [targets, calldatas];
}

View File

@ -0,0 +1,35 @@
import { ethers, network } from "hardhat";
import { addresses } from "./mainnet/addresses";
import { addresses as addressesPolygon } from "./polygon/addresses";
import { addresses as addressesArbitrum } from "./arbitrum/addresses";
import { addresses as addressesAvalanche } from "./avalanche/addresses";
import { abis } from "../constant/abis";
function getAddress(network: string | undefined) {
if (network === "polygon") return addressesPolygon.core.instaIndex;
else if (network === "arbitrum") return addressesArbitrum.core.instaIndex;
else if (network === "avalanche") return addressesAvalanche.core.instaIndex;
else return addresses.core.instaIndex;
}
export async function getMasterSigner() {
const [_, __, ___, wallet3] = await ethers.getSigners();
const instaIndex = new ethers.Contract(
getAddress(String(process.env.networkType)),
abis.core.instaIndex,
wallet3
);
const masterAddress = await instaIndex.master();
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [masterAddress],
});
await network.provider.send("hardhat_setBalance", [
masterAddress,
"0x8ac7230489e80000", // 1e19 wei
]);
return await ethers.getSigner(masterAddress);
}

View File

@ -0,0 +1,44 @@
import { promises as fs } from "fs";
import { join } from "path";
import { execScript } from "./command";
let start: number, end: number;
async function testRunner() {
const chain = ["avalanche", "mainnet", "polygon"];
start = Date.now();
for (let ch of chain) {
console.log(`📗Running test for %c${ch}: `, "blue");
let path: string;
const testsPath = join(__dirname, "../../test", ch);
await fs.access(testsPath);
const availableTests = await fs.readdir(testsPath);
if (availableTests.length !== 0) {
for (let test of availableTests) {
path = join(testsPath, test);
path += "/*";
await execScript({
cmd: "npx",
args: ["hardhat", "test", path],
env: {
networkType: ch,
},
});
}
}
}
end = Date.now();
}
testRunner()
.then(() =>
console.log(
`🙌 finished running the test, total time taken ${(end - start) /
1000} sec`
)
)
.catch((err) => console.error("❌ failed due to error: ", err));

View File

@ -1,6 +1,6 @@
const { ethers, network } = require("hardhat");
import { ethers, network } from "hardhat";
module.exports = async (accounts) => {
export const impersonateAccounts = async (accounts: any) => {
const signers = [];
for (const account of accounts) {
await network.provider.request({
@ -10,6 +10,5 @@ module.exports = async (accounts) => {
signers.push(await ethers.getSigner(account));
}
return signers;
};

View File

@ -0,0 +1,11 @@
export const addresses: Record<string, any> = {
connectors: {
"basic": "0xe5398f279175962E56fE4c5E0b62dc7208EF36c6",
"auth": "0xd1aff9f2acf800c876c409100d6f39aea93fc3d9",
"INSTAPOOL-A": "0x5806af7ab22e2916fa582ff05731bf7c682387b2",
},
core: {
"connectorsV2": "0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11",
"instaIndex": "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723",
},
};

View File

@ -0,0 +1,23 @@
export const tokens = {
eth: {
type: "token",
symbol: "ETH",
name: "Ethereum",
address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
decimals: 18,
},
dai: {
type: "token",
symbol: "DAI",
name: "DAI Stable",
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
decimals: 18,
},
usdc: {
type: "token",
symbol: "USDC",
name: "USD Coin",
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
decimals: 6,
},
};

View File

@ -1,4 +1,4 @@
module.exports = {
export const addresses: Record<string, any> = {
connectors: {
basic: "0x1cAF5EC802ca602E98139AD96A8f2B7BC524264E",
auth: "0xf6474aD0dA75A0dE15D2c915e601D9f754B9e6fe",
@ -7,4 +7,4 @@ module.exports = {
connectorsV2: "0x2A00684bFAb9717C21271E0751BCcb7d2D763c88",
instaIndex: "0xA9B99766E6C676Cf1975c0D3166F96C0848fF5ad",
},
};
};

View File

@ -0,0 +1,30 @@
export const tokens = {
matic: {
type: "token",
symbol: "MATIC",
name: "Matic",
address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
decimals: 18,
},
eth: {
type: "token",
symbol: "ETH",
name: "Ethereum",
address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
decimals: 18,
},
dai: {
type: "token",
symbol: "DAI",
name: "DAI Stable",
address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
decimals: 18,
},
usdc: {
type: "token",
symbol: "USDC",
name: "USD Coin",
address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
decimals: 6,
},
};

View File

@ -0,0 +1,68 @@
import inquirer from "inquirer";
import { promises as fs } from "fs";
import { join } from "path";
import { execScript } from "./command";
let start: number, end: number;
async function testRunner() {
const { chain } = await inquirer.prompt([
{
name: "chain",
message: "What chain do you want to run tests on?",
type: "list",
choices: ["mainnet", "polygon", "avalanche", "arbitrum"],
},
]);
const testsPath = join(__dirname, "../../test", chain);
await fs.access(testsPath);
const availableTests = await fs.readdir(testsPath);
if (availableTests.length === 0) {
throw new Error(`No tests available for ${chain}`);
}
const { testName } = await inquirer.prompt([
{
name: "testName",
message: "For which resolver you want to run the tests?",
type: "list",
choices: ["all", ...availableTests],
},
]);
start = Date.now();
let path: string;
if (testName === "all") {
for (let test of availableTests) {
path = join(testsPath, test);
path += "/*";
await execScript({
cmd: "npx",
args: ["hardhat", "test", path],
env: {
networkType: chain,
},
});
}
} else {
path = join(testsPath, testName);
path += "/*";
await execScript({
cmd: "npx",
args: ["hardhat", "test", path],
env: {
networkType: chain,
},
});
}
end = Date.now();
}
testRunner()
.then(() =>
console.log(
`🙌 finished the test runner, time taken ${(end - start) / 1000} sec`
)
)
.catch((err) => console.error("❌ failed due to error: ", err));

View File

@ -1,370 +0,0 @@
const fs = require('fs')
const path = require('path')
const forbiddenStrings = ['selfdestruct']
const getConnectorsList = async (connectorsRootsDirs) => {
try {
const connectors = []
for (let index = 0; index < connectorsRootsDirs.length; index++) {
const dirs = [connectorsRootsDirs[index]]
while (dirs.length) {
const currentDir = dirs.pop()
const subs = fs.readdirSync(currentDir, { withFileTypes: true })
for (let index = 0; index < subs.length; index++) {
const sub = subs[index]
if (sub.isFile() && sub.name === 'main.sol') {
connectors.push(currentDir)
} else if (sub.isDirectory()) {
dirs.push(`${currentDir}/${sub.name}`)
}
}
}
}
return connectors.map(dir => ({ path: dir }))
} catch (error) {
return Promise.reject(error)
}
}
const checkCodeForbidden = async (code, codePath) => {
try {
const forbidden = []
for (let i1 = 0; i1 < forbiddenStrings.length; i1++) {
const forbiddenStr = forbiddenStrings[i1]
const strs = code.split('\n')
for (let i2 = 0; i2 < strs.length; i2++) {
if (strs[i2].includes(forbiddenStr)) {
forbidden.push(`found '${forbiddenStr}' in ${codePath}:${i2 + 1}`)
}
}
}
return forbidden
} catch (error) {
return Promise.reject(error)
}
}
const checkForbidden = async (parentPath, codePath = './main.sol') => {
try {
if (codePath.startsWith('@')) {
codePath = path.resolve('node_modules', `./${codePath}`)
} else {
codePath = path.resolve(parentPath, codePath)
}
const code = fs.readFileSync(codePath, { encoding: 'utf8' })
const forbidden = await checkCodeForbidden(code, codePath)
if (code.includes('import')) {
const importsPathes = code
.split('\n')
.filter(str => str.includes('import') && str.includes('from') && str.includes('.sol'))
.map(str => str.split('from')[1].replace(/["; ]/gi, ''))
for (let index = 0; index < importsPathes.length; index++) {
const forbiddenErrors = await checkForbidden(
path.parse(codePath).dir,
importsPathes[index]
)
forbidden.push(...forbiddenErrors)
}
}
return codePath.endsWith('main.sol') ? { forbiddenErrors: forbidden, code } : forbidden
} catch (error) {
return Promise.reject(error)
}
}
const checkEvents = async (connector) => {
try {
const errors = []
const warnings = []
const eventsPath = `${connector.path}/events.sol`
const mainPath = `${connector.path}/main.sol`
if (connector.events.length) {
const eventNames = []
for (let i1 = 0; i1 < connector.mainEvents.length; i1++) {
const mainEvent = connector.mainEvents[i1]
const name = mainEvent.split('(')[0]
eventNames.push(name)
const event = connector.events.find(e => e.split('(')[0].split(' ')[1] === name)
if (event) {
const mainEventArgs = mainEvent.split('(')[1].split(')')[0].split(',').map(a => a.trim())
const eventArgs = event.split('(')[1].split(')')[0].split(',').map(a => a.trim())
if (mainEventArgs.length !== eventArgs.length) {
errors.push(`arguments amount don't match for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`)
continue
}
for (let i2 = 0; i2 < mainEventArgs.length; i2++) {
if (!mainEventArgs[i2].startsWith(eventArgs[i2].split(' ')[0])) {
errors.push(`invalid argument #${i2 + 1} for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`)
}
}
} else {
errors.push(`event ${name} missing at ${eventsPath}`)
}
}
if (connector.mainEvents.length < connector.events.length) {
const deprecatedEvents = connector.events.filter(e => {
let used = false
for (let index = 0; index < eventNames.length; index++) {
if (e.split('(')[0].split(' ')[1] === eventNames[index]) used = true
}
return !used
})
warnings.push(`${deprecatedEvents.map(e => e.split('(')[0].split(' ')[1]).join(', ')} event(s) not used at ${connector.path}/main.sol`)
}
} else {
warnings.push(`missing events file for ${connector.path}/main.sol`)
}
return { eventsErrors: errors, eventsWarnings: warnings }
} catch (error) {
return Promise.reject(error)
}
}
const getCommments = async (strs) => {
try {
const comments = []
let type
for (let index = strs.length - 1; index >= 0; index--) {
const str = strs[index]
if (!type) {
if (str.trim().startsWith('//')) {
type = 'single'
} else if (str.trim().startsWith('*/')) {
type = 'multiple'
}
}
if (type === 'single' && str.trim().startsWith('//')) {
comments.push(str.replace(/[/]/gi, '').trim())
} else if (type === 'multiple' && !str.trim().startsWith('/**') && !str.trim().startsWith('*/')) {
comments.push(str.replace(/[*]/gi, '').trim())
} else if (type === 'single' && !str.trim().startsWith('//')) {
break
} else if (type === 'multiple' && str.trim().startsWith('/**')) {
break
}
}
return comments
} catch (error) {
return Promise.reject(error)
}
}
const parseCode = async (connector) => {
try {
const strs = connector.code.split('\n')
const events = []
const eventsFirstLines = []
let func = []
let funcs = []
let event = []
let mainEvents = []
let firstLine
let mainEventsLines = []
for (let index = 0; index < strs.length; index++) {
const str = strs[index]
if (str.includes('function') && !str.trim().startsWith('//')) {
func = [str]
firstLine = index + 1
} else if (func.length && !str.trim().startsWith('//')) {
func.push(str)
}
if (func.length && str.startsWith(`${func[0].split('function')[0]}}`)) {
funcs.push({
raw: func.map(str => str.trim()).join(' '),
comments: await getCommments(strs.slice(0, firstLine)),
firstLine
})
func = []
}
}
const allPublicFuncs = funcs
.filter(({ raw }) => {
return raw.includes('external') || raw.includes('public')
})
.map(f => {
const name = f.raw.split('(')[0].split('function')[1].trim()
return {
...f,
name
}
})
funcs = allPublicFuncs
.filter(({ raw }) => {
if (raw.includes('returns')) {
const returns = raw.split('returns')[1].split('(')[1].split(')')[0]
return returns.includes('string') && returns.includes('bytes')
}
return false
})
.map(f => {
const args = f.raw.split('(')[1].split(')')[0].split(',')
.map(arg => arg.trim())
.filter(arg => arg !== '')
return {
...f,
args
}
})
const eventsPath = `${connector.path}/events.sol`
if (fs.existsSync(eventsPath)) {
mainEvents = funcs
.map(({ raw }) => raw.split('_eventName')[2].trim().split('"')[1])
.filter(raw => !!raw)
mainEventsLines = mainEvents.map(me => strs.findIndex(str => str.includes(me)) + 1)
const eventsCode = fs.readFileSync(eventsPath, { encoding: 'utf8' })
const eventsStrs = eventsCode.split('\n')
for (let index = 0; index < eventsStrs.length; index++) {
const str = eventsStrs[index]
if (str.includes('event')) {
event = [str]
firstLine = index + 1
} else if (event.length && !str.trim().startsWith('//')) {
event.push(str)
}
if (event.length && str.includes(')')) {
events.push(event.map(str => str.trim()).join(' '))
eventsFirstLines.push(firstLine)
event = []
}
}
}
return {
...connector,
events,
eventsFirstLines,
mainEvents,
mainEventsLines,
funcs,
allPublicFuncs
}
} catch (error) {
return Promise.reject(error)
}
}
const checkComments = async (connector) => {
try {
const errors = []
for (let i1 = 0; i1 < connector.funcs.length; i1++) {
const func = connector.funcs[i1]
for (let i2 = 0; i2 < func.args.length; i2++) {
const argName = func.args[i2].split(' ').pop()
if (!func.comments.some(
comment => comment.startsWith('@param') && comment.split(' ')[1] === argName
)) {
errors.push(`argument ${argName} has no @param for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`)
}
}
const reqs = ['@dev', '@notice']
for (let i3 = 0; i3 < reqs.length; i3++) {
if (!func.comments.some(comment => comment.startsWith(reqs[i3]))) {
errors.push(`no ${reqs[i3]} for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`)
}
}
}
return errors
} catch (error) {
return Promise.reject(error)
}
}
const checkPublicFuncs = async (connector) => {
try {
const errors = []
for (let i1 = 0; i1 < connector.allPublicFuncs.length; i1++) {
const { raw, firstLine, name } = connector.allPublicFuncs[i1]
if (!raw.includes('payable')) {
errors.push(`public function ${name} is not payable at ${connector.path}/main.sol:${firstLine}`)
}
}
return errors
} catch (error) {
return Promise.reject(error)
}
}
const checkName = async (connector) => {
try {
const strs = connector.code.split('\n')
let haveName = false
for (let index = strs.length - 1; index > 0; index--) {
const str = strs[index]
if (str.includes('string') && str.includes('public') && str.includes('name = ')) {
haveName = true
}
}
return haveName ? [] : [`name variable missing in ${connector.path}/main.sol`]
} catch (error) {
return Promise.reject(error)
}
}
const checkHeadComments = async (connector) => {
try {
const errors = []
const strs = connector.code.split('\n')
let haveTitle = false
let haveDev = false
for (let index = 0; index < strs.length; index++) {
if (!strs[index].includes('{')) {
if (strs[index].includes('@title')) haveTitle = true
if (strs[index].includes('@dev')) haveDev = true
} else {
break
}
}
if (!haveTitle) errors.push(`@title missing in ${connector.path}/main.sol`)
if (!haveDev) errors.push(`@dev missing in ${connector.path}/main.sol`)
return errors
} catch (error) {
return Promise.reject(error)
}
}
async function checkMain () {
try {
const connectorsRootsDirsDefault = ['mainnet', 'polygon'].map(v=> `contracts/${v}/connectors`)
const customPathArg = process.argv.find(a => a.startsWith('connector='))
const connectorsRootsDirs = customPathArg
? [customPathArg.slice(10)]
: connectorsRootsDirsDefault
const errors = []
const warnings = []
const connectors = await getConnectorsList(connectorsRootsDirs)
for (let index = 0; index < connectors.length; index++) {
const { forbiddenErrors, code } = await checkForbidden(connectors[index].path)
connectors[index].code = code
connectors[index] = await parseCode(connectors[index])
const { eventsErrors, eventsWarnings } = await checkEvents(connectors[index])
const commentsErrors = await checkComments(connectors[index])
const nameErrors = await checkName(connectors[index])
const headCommentsErrors = await checkHeadComments(connectors[index])
const publicFuncsErrors = await checkPublicFuncs(connectors[index])
errors.push(...forbiddenErrors)
errors.push(...eventsErrors)
errors.push(...commentsErrors)
errors.push(...nameErrors)
errors.push(...headCommentsErrors)
errors.push(...publicFuncsErrors)
warnings.push(...eventsWarnings)
}
if (errors.length) {
console.log('\x1b[31m%s\x1b[0m', `Total errors: ${errors.length}`)
errors.forEach(error => console.log('\x1b[31m%s\x1b[0m', error))
} else {
console.log('\x1b[32m%s\x1b[0m', 'No Errors Found')
}
if (warnings.length) {
console.log('\x1b[33m%s\x1b[0m', `Total warnings: ${warnings.length}`)
warnings.forEach(warning => console.log('\x1b[33m%s\x1b[0m', warning))
} else {
console.log('\x1b[32m%s\x1b[0m', 'No Warnings Found')
}
if (errors.length) return Promise.reject(errors.join('\n'))
} catch (error) {
console.error('check execution error:', error)
}
}
module.exports = checkMain

448
status-checks/check.ts Normal file
View File

@ -0,0 +1,448 @@
import * as fs from "fs";
import * as path from "path";
const forbiddenStrings: any = ["selfdestruct"];
const getConnectorsList= async (connectorsRootsDirs: string | any[]): Promise<Record<string, any>> => {
try {
const connectors = [];
for (let index = 0; index < connectorsRootsDirs.length; index++) {
const dirs = [connectorsRootsDirs[index]];
while (dirs.length) {
const currentDir = dirs.pop();
const subs = fs.readdirSync(currentDir, { withFileTypes: true });
for (let index = 0; index < subs.length; index++) {
const sub = subs[index];
if (sub.isFile() && sub.name === "main.sol") {
connectors.push(currentDir);
} else if (sub.isDirectory()) {
dirs.push(`${currentDir}/${sub.name}`);
}
}
}
}
return connectors.map((dir) => ({ path: dir }));
} catch (error) {
return Promise.reject(error);
}
};
const checkCodeForbidden = async (code: string, codePath: string) => {
try {
const forbidden = [];
for (let i1 = 0; i1 < forbiddenStrings.length; i1++) {
const forbiddenStr = forbiddenStrings[i1];
const strs = code.split("\n");
for (let i2 = 0; i2 < strs.length; i2++) {
if (strs[i2].includes(forbiddenStr)) {
forbidden.push(`found '${forbiddenStr}' in ${codePath}:${i2 + 1}`);
}
}
}
return forbidden;
} catch (error) {
return Promise.reject(error);
}
};
const checkForbidden = async (parentPath: string, codePath = "./main.sol") => {
try {
if (codePath.startsWith("@")) {
codePath = path.resolve("node_modules", `./${codePath}`);
} else {
codePath = path.resolve(parentPath, codePath);
}
const code = fs.readFileSync(codePath, { encoding: "utf8" });
const forbidden: any = await checkCodeForbidden(code, codePath);
if (code.includes("import")) {
const importsPathes = code
.split("\n")
.filter(
(str) =>
str.includes("import") &&
str.includes("from") &&
str.includes(".sol")
)
.map((str) => str.split("from")[1].replace(/["; ]/gi, ""));
for (let index = 0; index < importsPathes.length; index++) {
const forbiddenErrors = await checkForbidden(
path.parse(codePath).dir,
importsPathes[index]
);
forbidden.push(...forbiddenErrors);
}
}
return codePath.endsWith("main.sol")
? { forbiddenErrors: forbidden, code }
: forbidden;
} catch (error) {
return Promise.reject(error);
}
};
const checkEvents = async (connector: {
path: any;
events?: any;
mainEvents?: any;
mainEventsLines?: any;
}) => {
try {
const errors = [];
const warnings = [];
const eventsPath = `${connector.path}/events.sol`;
const mainPath = `${connector.path}/main.sol`;
if (connector.events.length) {
const eventNames = [];
for (let i1 = 0; i1 < connector.mainEvents.length; i1++) {
const mainEvent = connector.mainEvents[i1];
const name = mainEvent.split("(")[0];
eventNames.push(name);
const event = connector.events.find(
(e: string) => e.split("(")[0].split(" ")[1] === name
);
if (event) {
const mainEventArgs = mainEvent
.split("(")[1]
.split(")")[0]
.split(",")
.map((a: string) => a.trim());
const eventArgs = event
.split("(")[1]
.split(")")[0]
.split(",")
.map((a: string) => a.trim());
if (mainEventArgs.length !== eventArgs.length) {
errors.push(
`arguments amount don't match for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`
);
continue;
}
for (let i2 = 0; i2 < mainEventArgs.length; i2++) {
if (!mainEventArgs[i2].startsWith(eventArgs[i2].split(" ")[0])) {
errors.push(
`invalid argument #${i2 + 1} for ${name} at ${mainPath}:${
connector.mainEventsLines[i1]
}`
);
}
}
} else {
errors.push(`event ${name} missing at ${eventsPath}`);
}
}
if (connector.mainEvents.length < connector.events.length) {
const deprecatedEvents = connector.events.filter((e) => {
let used = false;
for (let index = 0; index < eventNames.length; index++) {
if (e.split("(")[0].split(" ")[1] === eventNames[index])
used = true;
}
return !used;
});
warnings.push(
`${deprecatedEvents
.map((e: string) => e.split("(")[0].split(" ")[1])
.join(", ")} event(s) not used at ${connector.path}/main.sol`
);
}
} else {
warnings.push(`missing events file for ${connector.path}/main.sol`);
}
return { eventsErrors: errors, eventsWarnings: warnings };
} catch (error) {
return Promise.reject(error);
}
};
const getCommments = async (strs: string | any[]) => {
try {
const comments = [];
let type: string;
for (let index = strs.length - 1; index >= 0; index--) {
const str = strs[index];
if (!type) {
if (str.trim().startsWith("//")) {
type = "single";
} else if (str.trim().startsWith("*/")) {
type = "multiple";
}
}
if (type === "single" && str.trim().startsWith("//")) {
comments.push(str.replace(/[/]/gi, "").trim());
} else if (
type === "multiple" &&
!str.trim().startsWith("/**") &&
!str.trim().startsWith("*/")
) {
comments.push(str.replace(/[*]/gi, "").trim());
} else if (type === "single" && !str.trim().startsWith("//")) {
break;
} else if (type === "multiple" && str.trim().startsWith("/**")) {
break;
}
}
return comments;
} catch (error) {
return Promise.reject(error);
}
};
const parseCode = async (connector: { path: any; code?: any }) => {
try {
const strs = connector.code.split("\n");
const events = [];
const eventsFirstLines = [];
let func = [];
let funcs = [];
let event = [];
let mainEvents = [];
let firstLine: number;
let mainEventsLines = [];
for (let index = 0; index < strs.length; index++) {
const str = strs[index];
if (str.includes("function") && !str.trim().startsWith("//")) {
func = [str];
firstLine = index + 1;
} else if (func.length && !str.trim().startsWith("//")) {
func.push(str);
}
if (func.length && str.startsWith(`${func[0].split("function")[0]}}`)) {
funcs.push({
raw: func.map((str) => str.trim()).join(" "),
comments: await getCommments(strs.slice(0, firstLine)),
firstLine,
});
func = [];
}
}
const allPublicFuncs = funcs
.filter(({ raw }) => {
return raw.includes("external") || raw.includes("public");
})
.map((f) => {
const name = f.raw
.split("(")[0]
.split("function")[1]
.trim();
return {
...f,
name,
};
});
funcs = allPublicFuncs
.filter(({ raw }) => {
if (raw.includes("returns")) {
const returns = raw
.split("returns")[1]
.split("(")[1]
.split(")")[0];
return returns.includes("string") && returns.includes("bytes");
}
return false;
})
.map((f) => {
const args = f.raw
.split("(")[1]
.split(")")[0]
.split(",")
.map((arg) => arg.trim())
.filter((arg) => arg !== "");
return {
...f,
args,
};
});
const eventsPath = `${connector.path}/events.sol`;
if (fs.existsSync(eventsPath)) {
mainEvents = funcs
.map(
({ raw }) =>
raw
.split("_eventName")[2]
.trim()
.split('"')[1]
)
.filter((raw) => !!raw);
mainEventsLines = mainEvents.map(
(me) => strs.findIndex((str: string | any[]) => str.includes(me)) + 1
);
const eventsCode = fs.readFileSync(eventsPath, { encoding: "utf8" });
const eventsStrs = eventsCode.split("\n");
for (let index = 0; index < eventsStrs.length; index++) {
const str = eventsStrs[index];
if (str.includes("event")) {
event = [str];
firstLine = index + 1;
} else if (event.length && !str.trim().startsWith("//")) {
event.push(str);
}
if (event.length && str.includes(")")) {
events.push(event.map((str) => str.trim()).join(" "));
eventsFirstLines.push(firstLine);
event = [];
}
}
}
return {
...connector,
events,
eventsFirstLines,
mainEvents,
mainEventsLines,
funcs,
allPublicFuncs,
};
} catch (error) {
return Promise.reject(error);
}
};
const checkComments = async (connector) => {
try {
const errors = [];
for (let i1 = 0; i1 < connector.funcs.length; i1++) {
const func = connector.funcs[i1];
for (let i2 = 0; i2 < func.args.length; i2++) {
const argName = func.args[i2].split(" ").pop();
if (
!func.comments.some(
(comment: string) =>
comment.startsWith("@param") && comment.split(" ")[1] === argName
)
) {
errors.push(
`argument ${argName} has no @param for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`
);
}
}
const reqs = ["@dev", "@notice"];
for (let i3 = 0; i3 < reqs.length; i3++) {
if (!func.comments.some((comment) => comment.startsWith(reqs[i3]))) {
errors.push(
`no ${reqs[i3]} for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`
);
}
}
}
return errors;
} catch (error) {
return Promise.reject(error);
}
};
const checkPublicFuncs = async (connector: {
path: any;
allPublicFuncs?: any;
}) => {
try {
const errors = [];
for (let i1 = 0; i1 < connector.allPublicFuncs.length; i1++) {
const { raw, firstLine, name } = connector.allPublicFuncs[i1];
if (!raw.includes("payable")) {
errors.push(
`public function ${name} is not payable at ${connector.path}/main.sol:${firstLine}`
);
}
}
return errors;
} catch (error) {
return Promise.reject(error);
}
};
const checkName = async (connector: { path: any; code?: any }) => {
try {
const strs = connector.code.split("\n");
let haveName = false;
for (let index = strs.length - 1; index > 0; index--) {
const str = strs[index];
if (
str.includes("string") &&
str.includes("public") &&
str.includes("name = ")
) {
haveName = true;
}
}
return haveName
? []
: [`name variable missing in ${connector.path}/main.sol`];
} catch (error) {
return Promise.reject(error);
}
};
const checkHeadComments = async (connector: { path: any; code?: any }) => {
try {
const errors = [];
const strs = connector.code.split("\n");
let haveTitle = false;
let haveDev = false;
for (let index = 0; index < strs.length; index++) {
if (!strs[index].includes("{")) {
if (strs[index].includes("@title")) haveTitle = true;
if (strs[index].includes("@dev")) haveDev = true;
} else {
break;
}
}
if (!haveTitle) errors.push(`@title missing in ${connector.path}/main.sol`);
if (!haveDev) errors.push(`@dev missing in ${connector.path}/main.sol`);
return errors;
} catch (error) {
return Promise.reject(error);
}
};
async function checkMain() {
try {
const connectorsRootsDirsDefault = ["mainnet", "polygon"].map(
(v) => `contracts/${v}/connectors`
);
const customPathArg = process.argv.find((a) => a.startsWith("connector="));
const connectorsRootsDirs = customPathArg
? [customPathArg.slice(10)]
: connectorsRootsDirsDefault;
const errors = [];
const warnings = [];
const connectors = await getConnectorsList(connectorsRootsDirs);
for (let index = 0; index < connectors.length; index++) {
const { forbiddenErrors, code } = await checkForbidden(
connectors[index].path
);
connectors[index].code = code;
connectors[index] = await parseCode(connectors[index]);
const { eventsErrors, eventsWarnings } = await checkEvents(
connectors[index]
);
const commentsErrors = await checkComments(connectors[index]);
const nameErrors = await checkName(connectors[index]);
const headCommentsErrors = await checkHeadComments(connectors[index]);
const publicFuncsErrors = await checkPublicFuncs(connectors[index]);
errors.push(...forbiddenErrors);
errors.push(...eventsErrors);
errors.push(...commentsErrors);
errors.push(...nameErrors);
errors.push(...headCommentsErrors);
errors.push(...publicFuncsErrors);
warnings.push(...eventsWarnings);
}
if (errors.length) {
console.log("\x1b[31m%s\x1b[0m", `Total errors: ${errors.length}`);
errors.forEach((error) => console.log("\x1b[31m%s\x1b[0m", error));
} else {
console.log("\x1b[32m%s\x1b[0m", "No Errors Found");
}
if (warnings.length) {
console.log("\x1b[33m%s\x1b[0m", `Total warnings: ${warnings.length}`);
warnings.forEach((warning) => console.log("\x1b[33m%s\x1b[0m", warning));
} else {
console.log("\x1b[32m%s\x1b[0m", "No Warnings Found");
}
if (errors.length) return Promise.reject(errors.join("\n"));
} catch (error) {
console.error("check execution error:", error);
}
}
export default checkMain;

Some files were not shown because too many files have changed in this diff Show More