diff --git a/contracts/mainnet/connectors/lixir/events.sol b/contracts/mainnet/connectors/lixir/events.sol new file mode 100644 index 00000000..6779e371 --- /dev/null +++ b/contracts/mainnet/connectors/lixir/events.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.7.0; + +contract Events { + event LogDeposit( + address indexed vault, + uint256 shares, + uint256 amount0In, + uint256 amount1In + ); + + event LogWithdraw( + address indexed vault, + uint256 amount0Out, + uint256 amount1Out + ); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/lixir/helpers.sol b/contracts/mainnet/connectors/lixir/helpers.sol new file mode 100644 index 00000000..ad0f621e --- /dev/null +++ b/contracts/mainnet/connectors/lixir/helpers.sol @@ -0,0 +1,78 @@ +pragma solidity ^0.7.6; +pragma abicoder v2; + +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 { + function _deposit( + address payable vaultAddress, + uint256 amount0Desired, + uint256 amount1Desired, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline + ) internal returns ( + uint256 shares, + uint256 amount0In, + uint256 amount1In + ) { + uint256 shares; + uint256 amount0In; + uint256 amount1In; + + if (msg.value > 0) { + ILixirVaultETH vault = ILixirVaultETH(vaultAddress); + + ( + shares, + amount0In, + amount1In + ) = vault.depositETH( + uint8(vault.WETH_TOKEN()) == 1 ? amount0Desired : amount1Desired, + uint8(vault.WETH_TOKEN()) == 1 ? amount0Min : amount1Min, + uint8(vault.WETH_TOKEN()) == 1 ? amount1Min : amount0Min, + recipient, + deadline + ); + } else { + ILixirVault vault = ILixirVault(vaultAddress); + ( + shares, + amount0In, + amount1In + ) = vault.deposit( + amount0Desired, + amount1Desired, + amount0Min, + amount1Min, + recipient, + deadline + ); + } + } + + function _withdraw( + address vaultAddress, + uint256 shares, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline + ) internal returns (uint256 amount0Out, uint256 amount1Out) { + ILixirVault vault = ILixirVault(vaultAddress); + ( + amount0Out, + amount1Out + ) = vault.withdraw( + shares, + amount0Min, + amount1Min, + recipient, + deadline + ); + } +} diff --git a/contracts/mainnet/connectors/lixir/interface.sol b/contracts/mainnet/connectors/lixir/interface.sol new file mode 100644 index 00000000..208a21f3 --- /dev/null +++ b/contracts/mainnet/connectors/lixir/interface.sol @@ -0,0 +1,204 @@ +pragma solidity ^0.7.6; + +import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol'; +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +interface IERC20Permit { + /** + * @dev Sets `value` as the allowance of `spender` over `owner`'s tokens, + * given `owner`'s signed approval. + * + * IMPORTANT: The same issues {IERC20-approve} has related to transaction + * ordering also apply here. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `deadline` must be a timestamp in the future. + * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` + * over the EIP712-formatted function arguments. + * - the signature must use ``owner``'s current nonce (see {nonces}). + * + * For more information on the signature format, see the + * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP + * section]. + */ + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external; + + /** + * @dev Returns the current nonce for `owner`. This value must be + * included whenever a signature is generated for {permit}. + * + * Every successful call to {permit} increases ``owner``'s nonce by one. This + * prevents a signature from being used multiple times. + */ + function nonces(address owner) external view returns (uint256); + + /** + * @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}. + */ + // solhint-disable-next-line func-name-mixedcase + function DOMAIN_SEPARATOR() external view returns (bytes32); +} + +interface ILixirVaultToken is IERC20, IERC20Permit { +} + +interface ILixirVault is ILixirVaultToken { + function initialize( + string memory name, + string memory symbol, + address _token0, + address _token1, + address _strategist, + address _keeper, + address _strategy + ) external; + + function token0() external view returns (IERC20); + + function token1() external view returns (IERC20); + + function activeFee() external view returns (uint24); + + function activePool() external view returns (IUniswapV3Pool); + + function performanceFee() external view returns (uint24); + + function strategist() external view returns (address); + + function strategy() external view returns (address); + + function keeper() external view returns (address); + + function setKeeper(address _keeper) external; + + function setStrategist(address _strategist) external; + + function setStrategy(address _strategy) external; + + function setPerformanceFee(uint24 newFee) external; + + function mainPosition() + external + view + returns (int24 tickLower, int24 tickUpper); + + function rangePosition() + external + view + returns (int24 tickLower, int24 tickUpper); + + function rebalance( + int24 mainTickLower, + int24 mainTickUpper, + int24 rangeTickLower0, + int24 rangeTickUpper0, + int24 rangeTickLower1, + int24 rangeTickUpper1, + uint24 fee + ) external; + + function withdraw( + uint256 shares, + uint256 amount0Min, + uint256 amount1Min, + address receiver, + uint256 deadline + ) external returns (uint256 amount0Out, uint256 amount1Out); + + function withdrawFrom( + address withdrawer, + uint256 shares, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline + ) external returns (uint256 amount0Out, uint256 amount1Out); + + function deposit( + uint256 amount0Desired, + uint256 amount1Desired, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline + ) + external + returns ( + uint256 shares, + uint256 amount0, + uint256 amount1 + ); + + function calculateTotals() + external + view + returns ( + uint256 total0, + uint256 total1, + uint128 mL, + uint128 rL + ); + + function calculateTotalsFromTick(int24 virtualTick) + external + view + returns ( + uint256 total0, + uint256 total1, + uint128 mL, + uint128 rL + ); +} + +interface ILixirVaultETH is ILixirVault { + + enum TOKEN {ZERO, ONE} + + function WETH_TOKEN() external view returns (TOKEN); + + function depositETH( + uint256 amountDesired, + uint256 amountEthMin, + uint256 amountMin, + address recipient, + uint256 deadline + ) + external + payable + returns ( + uint256 shares, + uint256 amountEthIn, + uint256 amountIn + ); + + function withdrawETHFrom( + address withdrawer, + uint256 shares, + uint256 amountEthMin, + uint256 amountMin, + address payable recipient, + uint256 deadline + ) external returns (uint256 amountEthOut, uint256 amountOut); + + function withdrawETH( + uint256 shares, + uint256 amountEthMin, + uint256 amountMin, + address payable recipient, + uint256 deadline + ) external returns (uint256 amountEthOut, uint256 amountOut); + + receive() external payable; +} diff --git a/contracts/mainnet/connectors/lixir/main.sol b/contracts/mainnet/connectors/lixir/main.sol new file mode 100644 index 00000000..59c07334 --- /dev/null +++ b/contracts/mainnet/connectors/lixir/main.sol @@ -0,0 +1,116 @@ +pragma solidity ^0.7.6; +pragma abicoder v2; + +/** + * @title Lixir Finance. + * @dev Automated Liquidity Concentrator. + */ + +import { TokenInterface } from "../../common/interfaces.sol"; +import { Helpers } from "./helpers.sol"; +import { Events } from "./events.sol"; + +abstract contract LixirResolver is Helpers, Events { + /** + * @dev Add liqudity to the vault + * @notice Mint Lixir Vault Tokens + * @param vault vault address + * @param amount0Desired amount of tokenA + * @param amount1Desired amount of tokenB + * @param amount0Min amount of tokenA + * @param amount1Min amount of tokenB + * @param recipient recipient of the Lixir Vault Tokens + * @param deadline unix timestamp + * @param getIds ID to retrieve amtA + * @param setId ID stores the amount of LP token + */ + function deposit( + address payable vault, + uint256 amount0Desired, + uint256 amount1Desired, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline, + uint256[] calldata getIds, + uint256 setId + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + amount0Desired = getUint(getIds[0], amount0Desired); + amount1Desired = getUint(getIds[1], amount1Desired); + + ( + uint256 shares, + uint256 amount0In, + uint256 amount1In + ) = _deposit( + vault, + amount0Desired, + amount1Desired, + amount0Min, + amount1Min, + recipient, + deadline + ); + + setUint(setId, shares); + + _eventName = "LogDeposit(address,uint256,uint256,uint256)"; + _eventParam = abi.encode( + vault, + shares, + amount0In, + amount1In + ); + } + + /** + * @dev Decrease Liquidity + * @notice Withdraw Liquidity from Lixir Vault + * @param vault Lixir vault address + * @param shares the amount of Lixir Vault Tokens to remove + * @param amount0Min Min amount of token0. + * @param amount1Min Min amount of token1. + * @param deadline unix timestamp + * @param getId ID to retrieve LP token amounts + * @param setIds stores the amount of output tokens + */ + function withdraw( + address vault, + uint256 shares, + uint256 amount0Min, + uint256 amount1Min, + address recipient, + uint256 deadline, + uint256 getId, + uint256[] calldata setIds + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + address vault = address(getUint(getId, uint256(vault))); // unsure of this... + + (uint256 amount0Out, uint256 amount1Out) = _withdraw( + vault, + shares, + amount0Min, + amount1Min, + recipient, + deadline + ); + + setUint(setIds[0], amount0Out); + setUint(setIds[1], amount1Out); + + _eventName = "LogWithdraw(address,uint256,uint256)"; + _eventParam = abi.encode(vault, amount0Out, amount1Out); + } +} + +contract ConnectV2Lixir is LixirResolver { + string public constant name = "Lixir-v1"; +} diff --git a/scripts/tests/addLiquidity.ts b/scripts/tests/addLiquidity.ts index f29d6374..69fdc368 100644 --- a/scripts/tests/addLiquidity.ts +++ b/scripts/tests/addLiquidity.ts @@ -9,15 +9,15 @@ const mineTx = async (tx: any) => { const tokenMapping: Record = { usdc: { - impersonateSigner: "0xfcb19e6a322b27c06842a71e8c725399f049ae3a", + impersonateSigner: "0x47ac0fb4f2d84898e4d9e7b4dab3c24507a6d503", address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", abi: [ - "function mint(address _to, uint256 _amount) external returns (bool);", + "function transfer(address to, uint value)" ], 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)); + await mineTx(contract.transfer(to, amt)); }, }, dai: { diff --git a/test/mainnet/lixir/lixir.test.ts b/test/mainnet/lixir/lixir.test.ts new file mode 100644 index 00000000..a976bd3b --- /dev/null +++ b/test/mainnet/lixir/lixir.test.ts @@ -0,0 +1,164 @@ +import { expect } from "chai"; +import hre from "hardhat"; +const { waffle, ethers, network } = hre; +const { provider, deployContract } = waffle; + +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addLiquidity } from "../../../scripts/tests/addLiquidity"; +import { addresses } from "../../../scripts/tests/mainnet/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import type { Signer, Contract } from "ethers"; + +import { ConnectV2Lixir__factory, ILixirVault__factory } from "../../../typechain"; + +const USDC_WETH_VAULT = "0x453A9f40a24DbE3CdB4edC988aF9bfE0F5602b15" + +const abiCoder = ethers.utils.defaultAbiCoder; + +describe("Lixir", function() { + const connectorName = "Lixir-v1"; + + let dsaWallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: Contract; + let vault: Contract; + + const wallets = provider.getWallets(); + const [wallet0, wallet1, wallet2, wallet3] = wallets; + before(async () => { + await network.provider.send("evm_setAutomine", [false]); + await network.provider.send("evm_setIntervalMining", [3000]); + await network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 13298611, + }, + }, + ], + }); + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt( + abis.core.connectorsV2, + addresses.core.connectorsV2 + ); + + vault = await ethers.getContractAt( + ILixirVault__factory.abi, + USDC_WETH_VAULT + ); + + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2Lixir__factory, + signer: masterSigner, + connectors: instaConnectorsV2, + }); + console.log("Connector address", connector.address); + }); + + it("Should have contracts deployed.", async function() { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function() { + it("Should build DSA v2", async function() { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + }); + + it("Deposit ETH & USDC into DSA wallet", async function() { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10"), + }); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + ethers.utils.parseEther("10") + ); + + await addLiquidity( + "usdc", + dsaWallet0.address, + 1000000 * 10**6 // USDC has 6 decimals + ); + }); + }); + + describe("Main", function() { + it("Should deposit successfully", async function() { + const usdcAmount = ethers.BigNumber.from(10**6).mul(4000); // ~1 ETH + const ethAmount = ethers.utils.parseEther("1"); // 1 ETH + + const getIds = ["0", "0"]; + const setId = "0"; + + const spells = [ + { + connector: connectorName, + method: "deposit", + args: [ // get these right + vault.address, + usdcAmount, + ethAmount, + 0, // any slippage lol + 0, // any slippage lol + dsaWallet0.address, + 1740297687, // high deadline + getIds, + setId, + ], + }, + ]; + + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), wallet1.address); + const receipt = await tx.wait(); + + + console.log(await vault.balanceOf(wallet1.address)); + // console.log(await vault.balanceOf(dsaWallet0.address)); + // const dsaLvtBalance = await vault.balanceOf(dsaWallet0.address); + // console.log(dsaLvtBalance); + // expect(dsaLvtBalance).gte(0); + }); + + // it("Should withdraw successfully", async function() { + // const getId = "0"; + // const setIds = ["0", "0"]; + + // const data = await nftManager.positions(tokenIds[0]); + // let data1 = await nftManager.positions(tokenIds[1]); + + // const spells = [ + // { + // connector: connectorName, + // method: "withdraw", + // args: [tokenIds[0], data.liquidity, 0, 0, getId, setIds], + // }, + // { + // connector: connectorName, + // method: "withdraw", + // args: [0, data1.liquidity, 0, 0, getId, setIds], + // }, + // ]; + + // const tx = await dsaWallet0 + // .connect(wallet0) + // .cast(...encodeSpells(spells), wallet1.address); + // const receipt = await tx.wait(); + + // data1 = await nftManager.positions(tokenIds[1]); + // expect(data1.liquidity.toNumber()).to.be.equals(0); + // }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 42a9309f..669c2a92 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12707,4 +12707,4 @@ "yn@3.1.1": "integrity" "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" "resolved" "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" - "version" "3.1.1" + "version" "3.1.1" \ No newline at end of file